Compare commits
68 Commits
b750cc3c67
...
2.1.1+39
Author | SHA1 | Date | |
---|---|---|---|
06dd3e092a | |||
82fe9e287a | |||
dc1c285de1 | |||
5a3313e94f | |||
61032c84f1 | |||
36a5b8fb39 | |||
3eda464e03 | |||
7a3ab6fd7d | |||
3d15c0b9f9 | |||
67a29b4305 | |||
594f57e0d3 | |||
d1eb51c596 | |||
85d2eff7f8 | |||
2375c46852 | |||
fd2eb5cda6 | |||
1256f440bd | |||
5b05ca67b6 | |||
95af7140cd | |||
77e9994204 | |||
3f6c186c13 | |||
9ac4a940dd | |||
ec050ab712 | |||
77e3ce8bcc | |||
f5dcf71e10 | |||
7fc18b40db | |||
8c8ab24c9e | |||
a319bd7f8c | |||
6427ec1f82 | |||
35dc7f4392 | |||
b50191970e | |||
1b69e6dd42 | |||
39fb4d474f | |||
392aebcad7 | |||
e9e3a4c474 | |||
7182336a0d | |||
be98fe133d | |||
e458943f56 | |||
eb125fc436 | |||
dc78f39969 | |||
f5c06bc89c | |||
d6d60e60a9 | |||
435b730f3b | |||
73468c5c6d | |||
8db6513eef | |||
65a8f1e6c3 | |||
2671ffad4b | |||
8a628823e0 | |||
94d19a1524 | |||
d98f6c8d18 | |||
6d0f62016a | |||
7e0faba5db | |||
7508a54907 | |||
2eb1f4b52b | |||
00678c0ac8 | |||
abc21f858b | |||
d67e33a41d | |||
4daff41b3e | |||
f92418ea4b | |||
89c912a35b | |||
09ad917e5d | |||
5c377dc0b6 | |||
8bdaf05223 | |||
e920bd954c | |||
e395ac87c5 | |||
026a4dfb27 | |||
df18370bde | |||
80a66136ce | |||
1f8d47f6c3 |
16
README.md
@ -1,16 +0,0 @@
|
|||||||
# surface
|
|
||||||
|
|
||||||
A new Flutter project.
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
This project is a starting point for a Flutter application.
|
|
||||||
|
|
||||||
A few resources to get you started if this is your first Flutter project:
|
|
||||||
|
|
||||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
|
||||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
|
||||||
|
|
||||||
For help getting started with Flutter development, view the
|
|
||||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
|
||||||
samples, guidance on mobile development, and a full API reference.
|
|
@ -9,7 +9,28 @@ plugins {
|
|||||||
id "dev.flutter.flutter-gradle-plugin"
|
id "dev.flutter.flutter-gradle-plugin"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'com.google.android.material:material:1.12.0'
|
||||||
|
implementation 'androidx.glance:glance:1.1.1'
|
||||||
|
implementation 'androidx.glance:glance-appwidget:1.1.1'
|
||||||
|
implementation 'androidx.compose.foundation:foundation-layout-android:1.7.6'
|
||||||
|
implementation 'com.google.code.gson:gson:2.10.1'
|
||||||
|
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
|
||||||
|
implementation 'io.coil-kt.coil3:coil-compose:3.0.4'
|
||||||
|
implementation 'io.coil-kt.coil3:coil-network-okhttp:3.0.4'
|
||||||
|
}
|
||||||
|
|
||||||
|
def keystoreProperties = new Properties()
|
||||||
|
def keystorePropertiesFile = rootProject.file('key.properties')
|
||||||
|
if (keystorePropertiesFile.exists()) {
|
||||||
|
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
buildFeatures {
|
||||||
|
compose true
|
||||||
|
}
|
||||||
|
|
||||||
namespace = "dev.solsynth.solian"
|
namespace = "dev.solsynth.solian"
|
||||||
compileSdk = flutter.compileSdkVersion
|
compileSdk = flutter.compileSdkVersion
|
||||||
ndkVersion = "27.0.12077973"
|
ndkVersion = "27.0.12077973"
|
||||||
@ -19,26 +40,41 @@ android {
|
|||||||
targetCompatibility JavaVersion.VERSION_17
|
targetCompatibility JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
|
composeOptions {
|
||||||
|
kotlinCompilerExtensionVersion = "1.4.8"
|
||||||
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = JavaVersion.VERSION_17
|
jvmTarget = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
|
||||||
applicationId = "dev.solsynth.solian"
|
applicationId = "dev.solsynth.solian"
|
||||||
// You can update the following values to match your application needs.
|
minSdk = 26
|
||||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
|
||||||
minSdk = flutter.minSdkVersion
|
|
||||||
targetSdk = flutter.targetSdkVersion
|
targetSdk = flutter.targetSdkVersion
|
||||||
versionCode = flutter.versionCode
|
versionCode = flutter.versionCode
|
||||||
versionName = flutter.versionName
|
versionName = flutter.versionName
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
signingConfigs {
|
||||||
release {
|
release {
|
||||||
// TODO: Add your own signing config for the release build.
|
keyAlias = keystoreProperties['keyAlias']
|
||||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
keyPassword = keystoreProperties['keyPassword']
|
||||||
signingConfig = signingConfigs.debug
|
storeFile = keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
|
||||||
|
storePassword = keystoreProperties['storePassword']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
debug {
|
||||||
|
debuggable true
|
||||||
|
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
release {
|
||||||
|
signingConfig = signingConfigs.release
|
||||||
|
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:label="Solian"
|
android:label="Solian"
|
||||||
@ -20,12 +21,44 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTask"
|
||||||
android:taskAffinity=""
|
android:taskAffinity=""
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
<!-- Widgets Indents -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="es.antonborri.home_widget.action.LAUNCH" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<!-- Sharing Intents -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="text/*" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="image/*" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="image/*" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="video/*" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="video/*" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
the Android process has started. This theme is visible to the user
|
the Android process has started. This theme is visible to the user
|
||||||
while the Flutter UI initializes. After that, this theme continues
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
@ -44,7 +77,30 @@
|
|||||||
<meta-data
|
<meta-data
|
||||||
android:name="flutterEmbedding"
|
android:name="flutterEmbedding"
|
||||||
android:value="2" />
|
android:value="2" />
|
||||||
|
|
||||||
|
<!-- Widgets -->
|
||||||
|
<receiver android:name=".widgets.CheckInWidgetReceiver"
|
||||||
|
android:label="Check In"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.appwidget.provider"
|
||||||
|
android:resource="@xml/check_in_widget" />
|
||||||
|
</receiver>
|
||||||
|
<receiver android:name=".widgets.RandomPostWidgetReceiver"
|
||||||
|
android:label="Random Post"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.appwidget.provider"
|
||||||
|
android:resource="@xml/random_post_widget" />
|
||||||
|
</receiver>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
<!-- Required to query activities that can process text, see:
|
<!-- Required to query activities that can process text, see:
|
||||||
https://developer.android.com/training/package-visibility and
|
https://developer.android.com/training/package-visibility and
|
||||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
package dev.solsynth.solian.data
|
||||||
|
|
||||||
|
import androidx.annotation.Keep
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class SolarPagination<T>(val count: Int, val data: List<T>)
|
@ -0,0 +1,35 @@
|
|||||||
|
package dev.solsynth.solian.data
|
||||||
|
|
||||||
|
import androidx.annotation.Keep
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class SolarPost(
|
||||||
|
val id: Int,
|
||||||
|
val body: SolarPostBody,
|
||||||
|
val publisher: SolarPublisher,
|
||||||
|
val publisherId: Int,
|
||||||
|
val createdAt: Instant,
|
||||||
|
val updatedAt: Instant,
|
||||||
|
val editedAt: Instant?,
|
||||||
|
val publishedAt: Instant?
|
||||||
|
)
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class SolarPostBody(
|
||||||
|
val content: String?,
|
||||||
|
val title: String?,
|
||||||
|
val description: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class SolarPublisher(
|
||||||
|
val id: Int,
|
||||||
|
val name: String,
|
||||||
|
val nick: String,
|
||||||
|
val description: String?,
|
||||||
|
val avatar: String?,
|
||||||
|
val banner: String?,
|
||||||
|
val createdAt: Instant,
|
||||||
|
val updatedAt: Instant
|
||||||
|
)
|
@ -0,0 +1,38 @@
|
|||||||
|
package dev.solsynth.solian.data
|
||||||
|
|
||||||
|
import androidx.annotation.Keep
|
||||||
|
import com.google.gson.JsonDeserializationContext
|
||||||
|
import com.google.gson.JsonDeserializer
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonParseException
|
||||||
|
import com.google.gson.JsonPrimitive
|
||||||
|
import com.google.gson.JsonSerializationContext
|
||||||
|
import com.google.gson.JsonSerializer
|
||||||
|
import java.lang.reflect.Type
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
class InstantAdapter : JsonSerializer<Instant?>,
|
||||||
|
JsonDeserializer<Instant?> {
|
||||||
|
override fun serialize(
|
||||||
|
src: Instant?,
|
||||||
|
typeOfSrc: Type?,
|
||||||
|
context: JsonSerializationContext?
|
||||||
|
): JsonElement {
|
||||||
|
return JsonPrimitive(formatter.format(src))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(JsonParseException::class)
|
||||||
|
override fun deserialize(
|
||||||
|
json: JsonElement,
|
||||||
|
typeOfT: Type?,
|
||||||
|
context: JsonDeserializationContext?
|
||||||
|
): Instant {
|
||||||
|
return Instant.parse(json.asString)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val formatter: DateTimeFormatter = DateTimeFormatter.ISO_INSTANT
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package dev.solsynth.solian.data
|
||||||
|
|
||||||
|
import androidx.annotation.Keep
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class SolarUser(
|
||||||
|
val id: Int,
|
||||||
|
val name: String,
|
||||||
|
val nick: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class SolarCheckInRecord(
|
||||||
|
val id: Int,
|
||||||
|
val resultTier: Int,
|
||||||
|
val resultExperience: Int,
|
||||||
|
val createdAt: Instant
|
||||||
|
)
|
@ -0,0 +1,128 @@
|
|||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.glance.GlanceId
|
||||||
|
import androidx.glance.GlanceModifier
|
||||||
|
import androidx.glance.GlanceTheme
|
||||||
|
import androidx.glance.action.clickable
|
||||||
|
import androidx.glance.appwidget.GlanceAppWidget
|
||||||
|
import androidx.glance.appwidget.provideContent
|
||||||
|
import androidx.glance.background
|
||||||
|
import androidx.glance.currentState
|
||||||
|
import androidx.glance.layout.Alignment
|
||||||
|
import androidx.glance.layout.Column
|
||||||
|
import androidx.glance.layout.Row
|
||||||
|
import androidx.glance.layout.Spacer
|
||||||
|
import androidx.glance.layout.fillMaxHeight
|
||||||
|
import androidx.glance.layout.fillMaxWidth
|
||||||
|
import androidx.glance.layout.height
|
||||||
|
import androidx.glance.layout.padding
|
||||||
|
import androidx.glance.state.GlanceStateDefinition
|
||||||
|
import androidx.glance.text.FontFamily
|
||||||
|
import androidx.glance.text.Text
|
||||||
|
import androidx.glance.text.TextStyle
|
||||||
|
import com.google.gson.FieldNamingPolicy
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
|
import dev.solsynth.solian.MainActivity
|
||||||
|
import dev.solsynth.solian.data.InstantAdapter
|
||||||
|
import dev.solsynth.solian.data.SolarCheckInRecord
|
||||||
|
import es.antonborri.home_widget.actionStartActivity
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
import java.time.ZoneId
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
class CheckInWidget : GlanceAppWidget() {
|
||||||
|
override val stateDefinition: GlanceStateDefinition<*>?
|
||||||
|
get() = HomeWidgetGlanceStateDefinition()
|
||||||
|
|
||||||
|
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
||||||
|
provideContent {
|
||||||
|
GlanceTheme {
|
||||||
|
GlanceContent(context, currentState())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun GlanceContent(context: Context, currentState: HomeWidgetGlanceState) {
|
||||||
|
val gson =
|
||||||
|
GsonBuilder()
|
||||||
|
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||||
|
.registerTypeAdapter(Instant::class.java, InstantAdapter())
|
||||||
|
.create()
|
||||||
|
val resultTierSymbols = listOf("大凶", "凶", "中平", "吉", "大吉")
|
||||||
|
|
||||||
|
val prefs = currentState.preferences
|
||||||
|
val checkInRaw: String? = prefs.getString("pas_check_in_record", null)
|
||||||
|
|
||||||
|
val checkIn: SolarCheckInRecord? =
|
||||||
|
checkInRaw?.let { checkInRaw ->
|
||||||
|
gson.fromJson(checkInRaw, SolarCheckInRecord::class.java)
|
||||||
|
} ?: null;
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = GlanceModifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.fillMaxHeight()
|
||||||
|
.background(GlanceTheme.colors.widgetBackground)
|
||||||
|
.padding(16.dp)
|
||||||
|
.clickable(
|
||||||
|
onClick = actionStartActivity<MainActivity>(
|
||||||
|
context,
|
||||||
|
Uri.parse("https://sn.solsynth.dev")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
if (checkIn != null) {
|
||||||
|
val dateFormatter = DateTimeFormatter.ofPattern("EEE, MM/dd")
|
||||||
|
|
||||||
|
val checkDate = checkIn.createdAt.atZone(ZoneId.of("UTC")).toLocalDate()
|
||||||
|
val currentDate = LocalDate.now()
|
||||||
|
if (checkDate.isEqual(currentDate)) {
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = resultTierSymbols[checkIn.resultTier],
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 17.sp,
|
||||||
|
color = GlanceTheme.colors.onSurface
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "+${checkIn.resultExperience} EXP",
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 13.sp,
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
color = GlanceTheme.colors.onSurface
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = GlanceModifier.height(8.dp))
|
||||||
|
Row(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
Text(
|
||||||
|
text = OffsetDateTime.ofInstant(
|
||||||
|
checkIn.createdAt,
|
||||||
|
ZoneId.systemDefault()
|
||||||
|
)
|
||||||
|
.format(dateFormatter),
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 11.sp,
|
||||||
|
color = GlanceTheme.colors.onSurface
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return@Column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "You haven't checked in today",
|
||||||
|
style = TextStyle(fontSize = 15.sp, color = GlanceTheme.colors.onSurface)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package dev.solsynth.solian.widgets
|
||||||
|
|
||||||
|
import CheckInWidget
|
||||||
|
import HomeWidgetGlanceWidgetReceiver
|
||||||
|
|
||||||
|
class CheckInWidgetReceiver : HomeWidgetGlanceWidgetReceiver<CheckInWidget>() {
|
||||||
|
override val glanceAppWidget = CheckInWidget()
|
||||||
|
}
|
@ -0,0 +1,168 @@
|
|||||||
|
import HomeWidgetGlanceState
|
||||||
|
import HomeWidgetGlanceStateDefinition
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.glance.GlanceId
|
||||||
|
import androidx.glance.GlanceModifier
|
||||||
|
import androidx.glance.GlanceTheme
|
||||||
|
import androidx.glance.action.clickable
|
||||||
|
import androidx.glance.appwidget.GlanceAppWidget
|
||||||
|
import androidx.glance.appwidget.provideContent
|
||||||
|
import androidx.glance.background
|
||||||
|
import androidx.glance.currentState
|
||||||
|
import androidx.glance.layout.Alignment
|
||||||
|
import androidx.glance.layout.Column
|
||||||
|
import androidx.glance.layout.Row
|
||||||
|
import androidx.glance.layout.Spacer
|
||||||
|
import androidx.glance.layout.fillMaxHeight
|
||||||
|
import androidx.glance.layout.fillMaxSize
|
||||||
|
import androidx.glance.layout.fillMaxWidth
|
||||||
|
import androidx.glance.layout.height
|
||||||
|
import androidx.glance.layout.padding
|
||||||
|
import androidx.glance.layout.width
|
||||||
|
import androidx.glance.state.GlanceStateDefinition
|
||||||
|
import androidx.glance.text.FontFamily
|
||||||
|
import androidx.glance.text.FontWeight
|
||||||
|
import androidx.glance.text.Text
|
||||||
|
import androidx.glance.text.TextStyle
|
||||||
|
import com.google.gson.FieldNamingPolicy
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
|
import dev.solsynth.solian.MainActivity
|
||||||
|
import dev.solsynth.solian.data.InstantAdapter
|
||||||
|
import dev.solsynth.solian.data.SolarPost
|
||||||
|
import es.antonborri.home_widget.actionStartActivity
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.ZoneId
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
class RandomPostWidget : GlanceAppWidget() {
|
||||||
|
override val stateDefinition: GlanceStateDefinition<*>?
|
||||||
|
get() = HomeWidgetGlanceStateDefinition()
|
||||||
|
|
||||||
|
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
||||||
|
provideContent {
|
||||||
|
GlanceTheme {
|
||||||
|
GlanceContent(context, currentState())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun GlanceContent(
|
||||||
|
context: Context,
|
||||||
|
currentState: HomeWidgetGlanceState,
|
||||||
|
) {
|
||||||
|
val prefs = currentState.preferences
|
||||||
|
val postRaw = prefs.getString("int_random_post", null)
|
||||||
|
|
||||||
|
val gson =
|
||||||
|
GsonBuilder()
|
||||||
|
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||||
|
.registerTypeAdapter(Instant::class.java, InstantAdapter())
|
||||||
|
.create()
|
||||||
|
|
||||||
|
val data: SolarPost? = postRaw?.let { postRaw ->
|
||||||
|
gson.fromJson(postRaw, SolarPost::class.java)
|
||||||
|
} ?: null;
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = GlanceModifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.fillMaxHeight()
|
||||||
|
.background(GlanceTheme.colors.widgetBackground)
|
||||||
|
.padding(16.dp)
|
||||||
|
.clickable(
|
||||||
|
onClick = actionStartActivity<MainActivity>(
|
||||||
|
context,
|
||||||
|
Uri.parse("https://sn.solsynth.dev/posts/${data!!.id}")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
if (data != null) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text(
|
||||||
|
text = data.publisher.nick,
|
||||||
|
style = TextStyle(fontSize = 15.sp, color = GlanceTheme.colors.onSurface)
|
||||||
|
)
|
||||||
|
Spacer(modifier = GlanceModifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
text = "@${data.publisher.name}",
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 13.sp,
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
color = GlanceTheme.colors.onSurface
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = GlanceModifier.height(8.dp))
|
||||||
|
|
||||||
|
if (data.body.title != null) {
|
||||||
|
Text(
|
||||||
|
text = data.body.title,
|
||||||
|
style = TextStyle(fontSize = 19.sp, color = GlanceTheme.colors.onSurface)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (data.body.description != null) {
|
||||||
|
Text(
|
||||||
|
text = data.body.description,
|
||||||
|
style = TextStyle(fontSize = 17.sp, color = GlanceTheme.colors.onSurface)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.body.title != null || data.body.description != null) {
|
||||||
|
Spacer(modifier = GlanceModifier.height(8.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = data.body.content ?: "No content",
|
||||||
|
style = TextStyle(fontSize = 15.sp, color = GlanceTheme.colors.onSurface),
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = GlanceModifier.height(8.dp))
|
||||||
|
|
||||||
|
|
||||||
|
Text(
|
||||||
|
LocalDateTime.ofInstant(data.createdAt, ZoneId.systemDefault())
|
||||||
|
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")),
|
||||||
|
style = TextStyle(fontSize = 13.sp, color = GlanceTheme.colors.onSurface),
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
"#${data.id}",
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 11.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = GlanceTheme.colors.onSurface
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return@Column;
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = GlanceModifier.fillMaxSize(),
|
||||||
|
verticalAlignment = Alignment.Vertical.CenterVertically,
|
||||||
|
horizontalAlignment = Alignment.Horizontal.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "No Recommendations",
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 17.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = GlanceTheme.colors.onSurface
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Open app to load some posts",
|
||||||
|
style = TextStyle(fontSize = 15.sp, color = GlanceTheme.colors.onSurface)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package dev.solsynth.solian.widgets
|
||||||
|
|
||||||
|
import RandomPostWidget
|
||||||
|
import HomeWidgetGlanceWidgetReceiver
|
||||||
|
|
||||||
|
class RandomPostWidgetReceiver : HomeWidgetGlanceWidgetReceiver<RandomPostWidget>() {
|
||||||
|
override val glanceAppWidget = RandomPostWidget()
|
||||||
|
}
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 537 B |
Before Width: | Height: | Size: 717 B After Width: | Height: | Size: 372 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 736 B |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 1.5 KiB |
@ -1,4 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="ic_launcher_background">#FFFFFFFF</color>
|
<color name="ic_launcher_background">#FFFFFFFF</color>
|
||||||
|
<color name="ic_notification_background">#00000000</color>
|
||||||
</resources>
|
</resources>
|
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
<style name="LaunchTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
|
||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
the Flutter engine draws its first frame -->
|
the Flutter engine draws its first frame -->
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
@ -16,7 +16,7 @@
|
|||||||
running.
|
running.
|
||||||
|
|
||||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
<style name="NormalTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||||
<item name="android:windowBackground">?android:colorBackground</item>
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
7
android/app/src/main/res/xml/check_in_widget.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:initialLayout="@layout/glance_default_loading_layout"
|
||||||
|
android:minWidth="40dp"
|
||||||
|
android:minHeight="40dp"
|
||||||
|
android:resizeMode="horizontal|vertical"
|
||||||
|
android:updatePeriodMillis="10000">
|
||||||
|
</appwidget-provider>
|
7
android/app/src/main/res/xml/random_post_widget.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:initialLayout="@layout/glance_default_loading_layout"
|
||||||
|
android:minWidth="240dp"
|
||||||
|
android:minHeight="40dp"
|
||||||
|
android:resizeMode="horizontal|vertical"
|
||||||
|
android:updatePeriodMillis="10000">
|
||||||
|
</appwidget-provider>
|
14
android/app/src/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
-keepclassmembers class kotlin.Metadata { *; }
|
||||||
|
-keep class dev.solsynth.solian.** { *; }
|
||||||
|
-keep public class dev.solsynth.solian.data.** { public *; }
|
||||||
|
-keepclassmembers class dev.solsynth.solian.data.** { *; }
|
||||||
|
|
||||||
|
-keepattributes *Annotation*
|
||||||
|
-keepattributes Signature
|
||||||
|
-keepattributes EnclosingMethod
|
||||||
|
|
||||||
|
-keep class com.google.gson.** { *; }
|
||||||
|
|
||||||
|
-keepclassmembers class * {
|
||||||
|
@com.google.gson.annotations.SerializedName <fields>;
|
||||||
|
}
|
@ -3,6 +3,15 @@ allprojects {
|
|||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
configurations.all {
|
||||||
|
resolutionStrategy {
|
||||||
|
eachDependency {
|
||||||
|
if ((requested.group == "androidx.work") && (requested.name.startsWith("work-runtime"))) {
|
||||||
|
useVersion("2.9.1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rootProject.buildDir = "../build"
|
rootProject.buildDir = "../build"
|
||||||
|
@ -18,7 +18,7 @@ pluginManagement {
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
id "com.android.application" version '8.7.2' apply false
|
id "com.android.application" version '8.7.3' apply false
|
||||||
// START: FlutterFire Configuration
|
// START: FlutterFire Configuration
|
||||||
id "com.google.gms.google-services" version "4.3.15" apply false
|
id "com.google.gms.google-services" version "4.3.15" apply false
|
||||||
id "com.google.firebase.crashlytics" version "2.8.1" apply false
|
id "com.google.firebase.crashlytics" version "2.8.1" apply false
|
||||||
|
@ -57,7 +57,7 @@
|
|||||||
"reply": "Reply",
|
"reply": "Reply",
|
||||||
"unset": "Unset",
|
"unset": "Unset",
|
||||||
"untitled": "Untitled",
|
"untitled": "Untitled",
|
||||||
"postDetail": "Post detail",
|
"postDetail": "Post Detail",
|
||||||
"postNoun": "Post",
|
"postNoun": "Post",
|
||||||
"postReadMore": "Read more",
|
"postReadMore": "Read more",
|
||||||
"postReadEstimate": "Est read time {}",
|
"postReadEstimate": "Est read time {}",
|
||||||
@ -139,6 +139,9 @@
|
|||||||
"fieldPostTitle": "Title",
|
"fieldPostTitle": "Title",
|
||||||
"fieldPostDescription": "Description",
|
"fieldPostDescription": "Description",
|
||||||
"fieldPostTags": "Tags",
|
"fieldPostTags": "Tags",
|
||||||
|
"fieldPostCategories": "Categories",
|
||||||
|
"fieldPostAlias": "Alias",
|
||||||
|
"fieldPostAliasHint": "Optional, used to represent the post in URL, should follow URL-Safe.",
|
||||||
"postPublish": "Publish",
|
"postPublish": "Publish",
|
||||||
"postPosted": "Post has been posted.",
|
"postPosted": "Post has been posted.",
|
||||||
"postPublishedAt": "Published At",
|
"postPublishedAt": "Published At",
|
||||||
@ -176,12 +179,18 @@
|
|||||||
"other": "{} comments"
|
"other": "{} comments"
|
||||||
},
|
},
|
||||||
"settingsAppearance": "Appearance",
|
"settingsAppearance": "Appearance",
|
||||||
|
"settingsAppBarTransparent": "Transparent App Bar",
|
||||||
|
"settingsAppBarTransparentDescription": "Enable transparent effect for the app bar.",
|
||||||
"settingsBackgroundImage": "Background Image",
|
"settingsBackgroundImage": "Background Image",
|
||||||
"settingsBackgroundImageDescription": "Set the background image that will be applied globally.",
|
"settingsBackgroundImageDescription": "Set the background image that will be applied globally.",
|
||||||
"settingsBackgroundImageClear": "Clear Existing Background Image",
|
"settingsBackgroundImageClear": "Clear Existing Background Image",
|
||||||
"settingsBackgroundImageClearDescription": "Reset the background image to blank.",
|
"settingsBackgroundImageClearDescription": "Reset the background image to blank.",
|
||||||
"settingsThemeMaterial3": "Use Material You Design",
|
"settingsThemeMaterial3": "Use Material You Design",
|
||||||
"settingsThemeMaterial3Description": "Set the application theme to Material 3 Design.",
|
"settingsThemeMaterial3Description": "Set the application theme to Material 3 Design.",
|
||||||
|
"settingsColorScheme": "Color Scheme",
|
||||||
|
"settingsColorSchemeDescription": "Set the application primary color.",
|
||||||
|
"settingsColorSeed": "Color Seed",
|
||||||
|
"settingsColorSeedDescription": "Select one of the present color schemes.",
|
||||||
"settingsNetwork": "Network",
|
"settingsNetwork": "Network",
|
||||||
"settingsNetworkServer": "HyperNet Server",
|
"settingsNetworkServer": "HyperNet Server",
|
||||||
"settingsNetworkServerDescription": "Set the HyperNet server address, choose ours or build your own.",
|
"settingsNetworkServerDescription": "Set the HyperNet server address, choose ours or build your own.",
|
||||||
@ -190,6 +199,13 @@
|
|||||||
"settingsNetworkServerPreset": "Present HyperNet Server",
|
"settingsNetworkServerPreset": "Present HyperNet Server",
|
||||||
"settingsNetworkServerPresetDescription": "You can choose one of our preset HyperNet server addresses from the list on the right.",
|
"settingsNetworkServerPresetDescription": "You can choose one of our preset HyperNet server addresses from the list on the right.",
|
||||||
"settingsNetworkServerSaved": "Server address saved.",
|
"settingsNetworkServerSaved": "Server address saved.",
|
||||||
|
"settingsPerformance": "Performance",
|
||||||
|
"settingsImageQuality": "Image Quality",
|
||||||
|
"settingsImageQualityDescription": "Set the image quality, it will affect the decoding speed of the image.",
|
||||||
|
"settingsImageQualityLowest": "Lowest",
|
||||||
|
"settingsImageQualityLow": "Low",
|
||||||
|
"settingsImageQualityMedium": "Medium",
|
||||||
|
"settingsImageQualityHigh": "High",
|
||||||
"settingsMisc": "Misc",
|
"settingsMisc": "Misc",
|
||||||
"settingsMiscAbout": "About",
|
"settingsMiscAbout": "About",
|
||||||
"settingsMiscAboutDescription": "View the version information of Solian.",
|
"settingsMiscAboutDescription": "View the version information of Solian.",
|
||||||
@ -362,7 +378,26 @@
|
|||||||
"dailyCheckNegativeHint5Description": "Lost connection at a crucial moment",
|
"dailyCheckNegativeHint5Description": "Lost connection at a crucial moment",
|
||||||
"dailyCheckNegativeHint6": "Going out",
|
"dailyCheckNegativeHint6": "Going out",
|
||||||
"dailyCheckNegativeHint6Description": "Forgot your umbrella and got caught in the rain",
|
"dailyCheckNegativeHint6Description": "Forgot your umbrella and got caught in the rain",
|
||||||
"happyBirthday": "Happy birthday, {}!",
|
"celebrateBirthday": "Happy birthday, {}!",
|
||||||
|
"celebrateMerryXmas": "Merry christmas, {}!",
|
||||||
|
"celebrateNewYear": "Happy new year, {}!",
|
||||||
|
"celebrateValentineDay": "Today is valentine's day, {}!",
|
||||||
|
"celebrateLaborDay": "Today is labor day, {}.",
|
||||||
|
"celebrateMotherDay": "Today is mother's day, {}.",
|
||||||
|
"celebrateChildrenDay": "Today is children's day, {}!",
|
||||||
|
"celebrateFatherDay": "Today is father's day, {}.",
|
||||||
|
"celebrateHalloween": "Happy halloween, {}!",
|
||||||
|
"celebrateThanksgiving": "Today is thanksgiving day, {}!",
|
||||||
|
"pendingBirthday": "Birthday in {}",
|
||||||
|
"pendingMerryXmas": "Christmas in {}",
|
||||||
|
"pendingNewYear": "New year in {}",
|
||||||
|
"pendingValentineDay": "Valentine's day in {}",
|
||||||
|
"pendingLaborDay": "Labor day in {}",
|
||||||
|
"pendingMotherDay": "Mother's day in {}",
|
||||||
|
"pendingChildrenDay": "Children's day in {}",
|
||||||
|
"pendingFatherDay": "Father's day in {}",
|
||||||
|
"pendingHalloween": "Halloween in {}",
|
||||||
|
"pendingThanksgiving": "Thanksgiving day in {}",
|
||||||
"friendNew": "Add Friend",
|
"friendNew": "Add Friend",
|
||||||
"friendRequests": "Friend Requests",
|
"friendRequests": "Friend Requests",
|
||||||
"friendRequestsDescription": {
|
"friendRequestsDescription": {
|
||||||
@ -439,10 +474,37 @@
|
|||||||
"publisherBlockHintDescription": "You are going to block this publisher's maintainer, this will also block publishers that run by the same user.",
|
"publisherBlockHintDescription": "You are going to block this publisher's maintainer, this will also block publishers that run by the same user.",
|
||||||
"userUnblocked": "{} has been unblocked.",
|
"userUnblocked": "{} has been unblocked.",
|
||||||
"userBlocked": "{} has been blocked.",
|
"userBlocked": "{} has been blocked.",
|
||||||
"postSharingViaPicture": "Capturing post as picture, please stand by...",
|
"postSharingViaPicture": "Capturing post as picture, please wait...",
|
||||||
"postImageShareReadMore": "Scan the QR code to read full post",
|
"postImageShareReadMore": "Scan the QR code to read full post",
|
||||||
"postImageShareAds": "Explore posts on the Solar Network",
|
"postImageShareAds": "Explore posts on the Solar Network",
|
||||||
"postShare": "Share",
|
"postShare": "Share",
|
||||||
"postShareImage": "Share via Image",
|
"postShareImage": "Share via Image",
|
||||||
"appInitializing": "Initializing"
|
"appInitializing": "Initializing",
|
||||||
|
"poweredBy": "Powered by {}",
|
||||||
|
"shareIntent": "Share",
|
||||||
|
"shareIntentDescription": "What do you want to do with the content you are sharing?",
|
||||||
|
"shareIntentPostStory": "Post a Story",
|
||||||
|
"updateAvailable": "Update Available",
|
||||||
|
"updateOngoing": "Updating, please wait...",
|
||||||
|
"custom": "Custom",
|
||||||
|
"colorSchemeIndigo": "Indigo",
|
||||||
|
"colorSchemeBlue": "Blue",
|
||||||
|
"colorSchemeGreen": "Green",
|
||||||
|
"colorSchemeYellow": "Yellow",
|
||||||
|
"colorSchemeOrange": "Orange",
|
||||||
|
"colorSchemeRed": "Red",
|
||||||
|
"colorSchemeWhite": "White",
|
||||||
|
"colorSchemeBlack": "Black",
|
||||||
|
"colorSchemeApplied": "Color scheme has been applied, may need restart the app to take effect.",
|
||||||
|
"postCategoryTechnology": "Technology",
|
||||||
|
"postCategoryGaming": "Gaming",
|
||||||
|
"postCategoryLife": "Life",
|
||||||
|
"postCategoryArts": "Arts",
|
||||||
|
"postCategorySports": "Sports",
|
||||||
|
"postCategoryMusic": "Music",
|
||||||
|
"postCategoryNews": "News",
|
||||||
|
"postCategoryKnowledge": "Knowledge",
|
||||||
|
"postCategoryLiterature": "Literature",
|
||||||
|
"postCategoryFunny": "Funny",
|
||||||
|
"postCategoryUncategorized": "Uncategorized"
|
||||||
}
|
}
|
||||||
|
@ -123,6 +123,9 @@
|
|||||||
"fieldPostTitle": "标题",
|
"fieldPostTitle": "标题",
|
||||||
"fieldPostDescription": "描述",
|
"fieldPostDescription": "描述",
|
||||||
"fieldPostTags": "标签",
|
"fieldPostTags": "标签",
|
||||||
|
"fieldPostCategories": "分类",
|
||||||
|
"fieldPostAlias": "别名",
|
||||||
|
"fieldPostAliasHint": "可选项,用于在 URL 中表示该帖子,应遵循 URL-Safe 的原则。",
|
||||||
"postPublish": "发布",
|
"postPublish": "发布",
|
||||||
"postPublishedAt": "发布于",
|
"postPublishedAt": "发布于",
|
||||||
"postPublishedUntil": "取消发布于",
|
"postPublishedUntil": "取消发布于",
|
||||||
@ -180,6 +183,12 @@
|
|||||||
"settingsBackgroundImageClearDescription": "将应用背景图重置为空白。",
|
"settingsBackgroundImageClearDescription": "将应用背景图重置为空白。",
|
||||||
"settingsThemeMaterial3": "使用 Material You 设计范式",
|
"settingsThemeMaterial3": "使用 Material You 设计范式",
|
||||||
"settingsThemeMaterial3Description": "将应用主题设置为 Material 3 设计范式的主题。",
|
"settingsThemeMaterial3Description": "将应用主题设置为 Material 3 设计范式的主题。",
|
||||||
|
"settingsAppBarTransparent": "透明顶栏",
|
||||||
|
"settingsAppBarTransparentDescription": "为顶栏启用透明效果。",
|
||||||
|
"settingsColorScheme": "主题色",
|
||||||
|
"settingsColorSchemeDescription": "设置应用主题色。",
|
||||||
|
"settingsColorSeed": "预设色彩主题",
|
||||||
|
"settingsColorSeedDescription": "选择一个预设色彩主题。",
|
||||||
"settingsNetwork": "网络",
|
"settingsNetwork": "网络",
|
||||||
"settingsNetworkServer": "HyperNet 服务器",
|
"settingsNetworkServer": "HyperNet 服务器",
|
||||||
"settingsNetworkServerDescription": "设置 HyperNet 服务器地址,选择我们提供的,或者自己搭建。",
|
"settingsNetworkServerDescription": "设置 HyperNet 服务器地址,选择我们提供的,或者自己搭建。",
|
||||||
@ -188,6 +197,13 @@
|
|||||||
"settingsNetworkServerPreset": "预设的 HyperNet 服务器",
|
"settingsNetworkServerPreset": "预设的 HyperNet 服务器",
|
||||||
"settingsNetworkServerPresetDescription": "你可以在旁边的列表中选择我们提供的预设 HyperNet 服务器地址。",
|
"settingsNetworkServerPresetDescription": "你可以在旁边的列表中选择我们提供的预设 HyperNet 服务器地址。",
|
||||||
"settingsNetworkServerSaved": "服务器地址已保存。",
|
"settingsNetworkServerSaved": "服务器地址已保存。",
|
||||||
|
"settingsPerformance": "性能",
|
||||||
|
"settingsImageQuality": "图片预览质量",
|
||||||
|
"settingsImageQualityDescription": "设置图片预览质量,会影响图片解码速度。",
|
||||||
|
"settingsImageQualityLowest": "极低",
|
||||||
|
"settingsImageQualityLow": "低",
|
||||||
|
"settingsImageQualityMedium": "中",
|
||||||
|
"settingsImageQualityHigh": "高",
|
||||||
"settingsMisc": "杂项",
|
"settingsMisc": "杂项",
|
||||||
"settingsMiscAbout": "关于",
|
"settingsMiscAbout": "关于",
|
||||||
"settingsMiscAboutDescription": "查看 Solian 的版本信息。",
|
"settingsMiscAboutDescription": "查看 Solian 的版本信息。",
|
||||||
@ -360,7 +376,26 @@
|
|||||||
"dailyCheckNegativeHint5Description": "关键时刻断网",
|
"dailyCheckNegativeHint5Description": "关键时刻断网",
|
||||||
"dailyCheckNegativeHint6": "出门",
|
"dailyCheckNegativeHint6": "出门",
|
||||||
"dailyCheckNegativeHint6Description": "忘带伞遇上大雨",
|
"dailyCheckNegativeHint6Description": "忘带伞遇上大雨",
|
||||||
"happyBirthday": "生日快乐,{}!",
|
"celebrateBirthday": "生日快乐,{}!",
|
||||||
|
"celebrateMerryXmas": "圣诞快乐,{}!",
|
||||||
|
"celebrateNewYear": "新年快乐,{}!",
|
||||||
|
"celebrateValentineDay": "今天是情人节,{}!",
|
||||||
|
"celebrateLaborDay": "今天是劳动节,{}。",
|
||||||
|
"celebrateMotherDay": "今天是母亲节,{}。",
|
||||||
|
"celebrateChildrenDay": "今天是儿童节,{}!",
|
||||||
|
"celebrateFatherDay": "今天是父亲节,{}。",
|
||||||
|
"celebrateHalloween": "快乐在圣诞节,{}!",
|
||||||
|
"celebrateThanksgiving": "今天是感恩节,{}!",
|
||||||
|
"pendingBirthday": "{} 过生日",
|
||||||
|
"pendingMerryXmas": "{} 过圣诞节",
|
||||||
|
"pendingNewYear": "{} 跨年",
|
||||||
|
"pendingValentineDay": "{} 过情人节",
|
||||||
|
"pendingLaborDay": "{} 过劳动节",
|
||||||
|
"pendingMotherDay": "{} 过母亲节",
|
||||||
|
"pendingChildrenDay": "{} 过儿童节",
|
||||||
|
"pendingFatherDay": "{} 过父亲节",
|
||||||
|
"pendingHalloween": "{} 过圣诞节",
|
||||||
|
"pendingThanksgiving": "{} 过感恩节",
|
||||||
"friendNew": "添加好友",
|
"friendNew": "添加好友",
|
||||||
"friendRequests": "好友请求",
|
"friendRequests": "好友请求",
|
||||||
"friendRequestsDescription": {
|
"friendRequestsDescription": {
|
||||||
@ -399,7 +434,7 @@
|
|||||||
"accountStatus": "状态",
|
"accountStatus": "状态",
|
||||||
"accountStatusOnline": "在线",
|
"accountStatusOnline": "在线",
|
||||||
"accountStatusOffline": "离线",
|
"accountStatusOffline": "离线",
|
||||||
"accountStatusLastSeen": "最后一次在 {} 上线",
|
"accountStatusLastSeen": "最后一次上线于 {}",
|
||||||
"postArticle": "Solar Network 上的文章",
|
"postArticle": "Solar Network 上的文章",
|
||||||
"postStory": "Solar Network 上的故事",
|
"postStory": "Solar Network 上的故事",
|
||||||
"articleWrittenAt": "发表于 {}",
|
"articleWrittenAt": "发表于 {}",
|
||||||
@ -442,5 +477,32 @@
|
|||||||
"postImageShareAds": "来 Solar Network 探索更多有趣帖子",
|
"postImageShareAds": "来 Solar Network 探索更多有趣帖子",
|
||||||
"postShare": "分享",
|
"postShare": "分享",
|
||||||
"postShareImage": "分享帖图",
|
"postShareImage": "分享帖图",
|
||||||
"appInitializing": "正在初始化"
|
"appInitializing": "正在初始化",
|
||||||
|
"poweredBy": "由 {} 提供支持",
|
||||||
|
"shareIntent": "分享",
|
||||||
|
"shareIntentDescription": "您想对您分享的内容做些什么?",
|
||||||
|
"shareIntentPostStory": "发布动态",
|
||||||
|
"updateAvailable": "检测到更新可用",
|
||||||
|
"updateOngoing": "正在更新,请稍后……",
|
||||||
|
"custom": "自定义",
|
||||||
|
"colorSchemeIndigo": "靛蓝",
|
||||||
|
"colorSchemeBlue": "蓝色",
|
||||||
|
"colorSchemeGreen": "绿色",
|
||||||
|
"colorSchemeYellow": "黄色",
|
||||||
|
"colorSchemeOrange": "橙色",
|
||||||
|
"colorSchemeRed": "红色",
|
||||||
|
"colorSchemeWhite": "白色",
|
||||||
|
"colorSchemeBlack": "黑色",
|
||||||
|
"colorSchemeApplied": "主题色已应用,可能需要重启来生效。",
|
||||||
|
"postCategoryTechnology": "技术",
|
||||||
|
"postCategoryGaming": "游戏",
|
||||||
|
"postCategoryLife": "生活",
|
||||||
|
"postCategoryArts": "艺术",
|
||||||
|
"postCategorySports": "体育",
|
||||||
|
"postCategoryMusic": "音乐",
|
||||||
|
"postCategoryNews": "新闻",
|
||||||
|
"postCategoryKnowledge": "知识",
|
||||||
|
"postCategoryLiterature": "文学",
|
||||||
|
"postCategoryFunny": "搞笑",
|
||||||
|
"postCategoryUncategorized": "未分类"
|
||||||
}
|
}
|
||||||
|
@ -123,6 +123,9 @@
|
|||||||
"fieldPostTitle": "標題",
|
"fieldPostTitle": "標題",
|
||||||
"fieldPostDescription": "描述",
|
"fieldPostDescription": "描述",
|
||||||
"fieldPostTags": "標籤",
|
"fieldPostTags": "標籤",
|
||||||
|
"fieldPostCategories": "分類",
|
||||||
|
"fieldPostAlias": "別名",
|
||||||
|
"fieldPostAliasHint": "可選項,用於在 URL 中表示該帖子,應遵循 URL-Safe 的原則。",
|
||||||
"postPublish": "發佈",
|
"postPublish": "發佈",
|
||||||
"postPublishedAt": "發佈於",
|
"postPublishedAt": "發佈於",
|
||||||
"postPublishedUntil": "取消發佈於",
|
"postPublishedUntil": "取消發佈於",
|
||||||
@ -180,6 +183,12 @@
|
|||||||
"settingsBackgroundImageClearDescription": "將應用背景圖重置為空白。",
|
"settingsBackgroundImageClearDescription": "將應用背景圖重置為空白。",
|
||||||
"settingsThemeMaterial3": "使用 Material You 設計範式",
|
"settingsThemeMaterial3": "使用 Material You 設計範式",
|
||||||
"settingsThemeMaterial3Description": "將應用主題設置為 Material 3 設計範式的主題。",
|
"settingsThemeMaterial3Description": "將應用主題設置為 Material 3 設計範式的主題。",
|
||||||
|
"settingsAppBarTransparent": "透明頂欄",
|
||||||
|
"settingsAppBarTransparentDescription": "為頂欄啓用透明效果。",
|
||||||
|
"settingsColorScheme": "主題色",
|
||||||
|
"settingsColorSchemeDescription": "設置應用主題色。",
|
||||||
|
"settingsColorSeed": "預設色彩主題",
|
||||||
|
"settingsColorSeedDescription": "選擇一個預設色彩主題。",
|
||||||
"settingsNetwork": "網絡",
|
"settingsNetwork": "網絡",
|
||||||
"settingsNetworkServer": "HyperNet 服務器",
|
"settingsNetworkServer": "HyperNet 服務器",
|
||||||
"settingsNetworkServerDescription": "設置 HyperNet 服務器地址,選擇我們提供的,或者自己搭建。",
|
"settingsNetworkServerDescription": "設置 HyperNet 服務器地址,選擇我們提供的,或者自己搭建。",
|
||||||
@ -188,6 +197,13 @@
|
|||||||
"settingsNetworkServerPreset": "預設的 HyperNet 服務器",
|
"settingsNetworkServerPreset": "預設的 HyperNet 服務器",
|
||||||
"settingsNetworkServerPresetDescription": "你可以在旁邊的列表中選擇我們提供的預設 HyperNet 服務器地址。",
|
"settingsNetworkServerPresetDescription": "你可以在旁邊的列表中選擇我們提供的預設 HyperNet 服務器地址。",
|
||||||
"settingsNetworkServerSaved": "服務器地址已保存。",
|
"settingsNetworkServerSaved": "服務器地址已保存。",
|
||||||
|
"settingsPerformance": "性能",
|
||||||
|
"settingsImageQuality": "圖片預覽質量",
|
||||||
|
"settingsImageQualityDescription": "設置圖片預覽質量,會影響圖片解碼速度。",
|
||||||
|
"settingsImageQualityLowest": "極低",
|
||||||
|
"settingsImageQualityLow": "低",
|
||||||
|
"settingsImageQualityMedium": "中",
|
||||||
|
"settingsImageQualityHigh": "高",
|
||||||
"settingsMisc": "雜項",
|
"settingsMisc": "雜項",
|
||||||
"settingsMiscAbout": "關於",
|
"settingsMiscAbout": "關於",
|
||||||
"settingsMiscAboutDescription": "查看 Solian 的版本信息。",
|
"settingsMiscAboutDescription": "查看 Solian 的版本信息。",
|
||||||
@ -360,7 +376,26 @@
|
|||||||
"dailyCheckNegativeHint5Description": "關鍵時刻斷網",
|
"dailyCheckNegativeHint5Description": "關鍵時刻斷網",
|
||||||
"dailyCheckNegativeHint6": "出門",
|
"dailyCheckNegativeHint6": "出門",
|
||||||
"dailyCheckNegativeHint6Description": "忘帶傘遇上大雨",
|
"dailyCheckNegativeHint6Description": "忘帶傘遇上大雨",
|
||||||
"happyBirthday": "生日快樂,{}!",
|
"celebrateBirthday": "生日快樂,{}!",
|
||||||
|
"celebrateMerryXmas": "聖誕快樂,{}!",
|
||||||
|
"celebrateNewYear": "新年快樂,{}!",
|
||||||
|
"celebrateValentineDay": "今天是情人節,{}!",
|
||||||
|
"celebrateLaborDay": "今天是勞動節,{}。",
|
||||||
|
"celebrateMotherDay": "今天是母親節,{}。",
|
||||||
|
"celebrateChildrenDay": "今天是兒童節,{}!",
|
||||||
|
"celebrateFatherDay": "今天是父親節,{}。",
|
||||||
|
"celebrateHalloween": "快樂在聖誕節,{}!",
|
||||||
|
"celebrateThanksgiving": "今天是感恩節,{}!",
|
||||||
|
"pendingBirthday": "{} 過生日",
|
||||||
|
"pendingMerryXmas": "{} 過聖誕節",
|
||||||
|
"pendingNewYear": "{} 跨年",
|
||||||
|
"pendingValentineDay": "{} 過情人節",
|
||||||
|
"pendingLaborDay": "{} 過勞動節",
|
||||||
|
"pendingMotherDay": "{} 過母親節",
|
||||||
|
"pendingChildrenDay": "{} 過兒童節",
|
||||||
|
"pendingFatherDay": "{} 過父親節",
|
||||||
|
"pendingHalloween": "{} 過聖誕節",
|
||||||
|
"pendingThanksgiving": "{} 過感恩節",
|
||||||
"friendNew": "添加好友",
|
"friendNew": "添加好友",
|
||||||
"friendRequests": "好友請求",
|
"friendRequests": "好友請求",
|
||||||
"friendRequestsDescription": {
|
"friendRequestsDescription": {
|
||||||
@ -399,7 +434,7 @@
|
|||||||
"accountStatus": "狀態",
|
"accountStatus": "狀態",
|
||||||
"accountStatusOnline": "在線",
|
"accountStatusOnline": "在線",
|
||||||
"accountStatusOffline": "離線",
|
"accountStatusOffline": "離線",
|
||||||
"accountStatusLastSeen": "最後一次在 {} 上線",
|
"accountStatusLastSeen": "最後一次上線於 {}",
|
||||||
"postArticle": "Solar Network 上的文章",
|
"postArticle": "Solar Network 上的文章",
|
||||||
"postStory": "Solar Network 上的故事",
|
"postStory": "Solar Network 上的故事",
|
||||||
"articleWrittenAt": "發表於 {}",
|
"articleWrittenAt": "發表於 {}",
|
||||||
@ -441,5 +476,33 @@
|
|||||||
"postImageShareReadMore": "掃描右側 QRCode 查看全文",
|
"postImageShareReadMore": "掃描右側 QRCode 查看全文",
|
||||||
"postImageShareAds": "來 Solar Network 探索更多有趣帖子",
|
"postImageShareAds": "來 Solar Network 探索更多有趣帖子",
|
||||||
"postShare": "分享",
|
"postShare": "分享",
|
||||||
"postShareImage": "分享帖圖"
|
"postShareImage": "分享帖圖",
|
||||||
|
"appInitializing": "正在初始化",
|
||||||
|
"poweredBy": "由 {} 提供支持",
|
||||||
|
"shareIntent": "分享",
|
||||||
|
"shareIntentDescription": "您想對您分享的內容做些什麼?",
|
||||||
|
"shareIntentPostStory": "發佈動態",
|
||||||
|
"updateAvailable": "檢測到更新可用",
|
||||||
|
"updateOngoing": "正在更新,請稍後……",
|
||||||
|
"custom": "自定義",
|
||||||
|
"colorSchemeIndigo": "靛藍",
|
||||||
|
"colorSchemeBlue": "藍色",
|
||||||
|
"colorSchemeGreen": "綠色",
|
||||||
|
"colorSchemeYellow": "黃色",
|
||||||
|
"colorSchemeOrange": "橙色",
|
||||||
|
"colorSchemeRed": "紅色",
|
||||||
|
"colorSchemeWhite": "白色",
|
||||||
|
"colorSchemeBlack": "黑色",
|
||||||
|
"colorSchemeApplied": "主題色已應用,可能需要重啓來生效。",
|
||||||
|
"postCategoryTechnology": "技術",
|
||||||
|
"postCategoryGaming": "遊戲",
|
||||||
|
"postCategoryLife": "生活",
|
||||||
|
"postCategoryArts": "藝術",
|
||||||
|
"postCategorySports": "體育",
|
||||||
|
"postCategoryMusic": "音樂",
|
||||||
|
"postCategoryNews": "新聞",
|
||||||
|
"postCategoryKnowledge": "知識",
|
||||||
|
"postCategoryLiterature": "文學",
|
||||||
|
"postCategoryFunny": "搞笑",
|
||||||
|
"postCategoryUncategorized": "未分類"
|
||||||
}
|
}
|
||||||
|
@ -123,6 +123,9 @@
|
|||||||
"fieldPostTitle": "標題",
|
"fieldPostTitle": "標題",
|
||||||
"fieldPostDescription": "描述",
|
"fieldPostDescription": "描述",
|
||||||
"fieldPostTags": "標籤",
|
"fieldPostTags": "標籤",
|
||||||
|
"fieldPostCategories": "分類",
|
||||||
|
"fieldPostAlias": "別名",
|
||||||
|
"fieldPostAliasHint": "可選項,用於在 URL 中表示該帖子,應遵循 URL-Safe 的原則。",
|
||||||
"postPublish": "釋出",
|
"postPublish": "釋出",
|
||||||
"postPublishedAt": "釋出於",
|
"postPublishedAt": "釋出於",
|
||||||
"postPublishedUntil": "取消釋出於",
|
"postPublishedUntil": "取消釋出於",
|
||||||
@ -180,6 +183,12 @@
|
|||||||
"settingsBackgroundImageClearDescription": "將應用背景圖重置為空白。",
|
"settingsBackgroundImageClearDescription": "將應用背景圖重置為空白。",
|
||||||
"settingsThemeMaterial3": "使用 Material You 設計正規化",
|
"settingsThemeMaterial3": "使用 Material You 設計正規化",
|
||||||
"settingsThemeMaterial3Description": "將應用主題設定為 Material 3 設計正規化的主題。",
|
"settingsThemeMaterial3Description": "將應用主題設定為 Material 3 設計正規化的主題。",
|
||||||
|
"settingsAppBarTransparent": "透明頂欄",
|
||||||
|
"settingsAppBarTransparentDescription": "為頂欄啟用透明效果。",
|
||||||
|
"settingsColorScheme": "主題色",
|
||||||
|
"settingsColorSchemeDescription": "設定應用主題色。",
|
||||||
|
"settingsColorSeed": "預設色彩主題",
|
||||||
|
"settingsColorSeedDescription": "選擇一個預設色彩主題。",
|
||||||
"settingsNetwork": "網路",
|
"settingsNetwork": "網路",
|
||||||
"settingsNetworkServer": "HyperNet 伺服器",
|
"settingsNetworkServer": "HyperNet 伺服器",
|
||||||
"settingsNetworkServerDescription": "設定 HyperNet 伺服器地址,選擇我們提供的,或者自己搭建。",
|
"settingsNetworkServerDescription": "設定 HyperNet 伺服器地址,選擇我們提供的,或者自己搭建。",
|
||||||
@ -188,6 +197,13 @@
|
|||||||
"settingsNetworkServerPreset": "預設的 HyperNet 伺服器",
|
"settingsNetworkServerPreset": "預設的 HyperNet 伺服器",
|
||||||
"settingsNetworkServerPresetDescription": "你可以在旁邊的列表中選擇我們提供的預設 HyperNet 伺服器地址。",
|
"settingsNetworkServerPresetDescription": "你可以在旁邊的列表中選擇我們提供的預設 HyperNet 伺服器地址。",
|
||||||
"settingsNetworkServerSaved": "伺服器地址已儲存。",
|
"settingsNetworkServerSaved": "伺服器地址已儲存。",
|
||||||
|
"settingsPerformance": "效能",
|
||||||
|
"settingsImageQuality": "圖片預覽質量",
|
||||||
|
"settingsImageQualityDescription": "設定圖片預覽質量,會影響圖片解碼速度。",
|
||||||
|
"settingsImageQualityLowest": "極低",
|
||||||
|
"settingsImageQualityLow": "低",
|
||||||
|
"settingsImageQualityMedium": "中",
|
||||||
|
"settingsImageQualityHigh": "高",
|
||||||
"settingsMisc": "雜項",
|
"settingsMisc": "雜項",
|
||||||
"settingsMiscAbout": "關於",
|
"settingsMiscAbout": "關於",
|
||||||
"settingsMiscAboutDescription": "檢視 Solian 的版本資訊。",
|
"settingsMiscAboutDescription": "檢視 Solian 的版本資訊。",
|
||||||
@ -360,7 +376,26 @@
|
|||||||
"dailyCheckNegativeHint5Description": "關鍵時刻斷網",
|
"dailyCheckNegativeHint5Description": "關鍵時刻斷網",
|
||||||
"dailyCheckNegativeHint6": "出門",
|
"dailyCheckNegativeHint6": "出門",
|
||||||
"dailyCheckNegativeHint6Description": "忘帶傘遇上大雨",
|
"dailyCheckNegativeHint6Description": "忘帶傘遇上大雨",
|
||||||
"happyBirthday": "生日快樂,{}!",
|
"celebrateBirthday": "生日快樂,{}!",
|
||||||
|
"celebrateMerryXmas": "聖誕快樂,{}!",
|
||||||
|
"celebrateNewYear": "新年快樂,{}!",
|
||||||
|
"celebrateValentineDay": "今天是情人節,{}!",
|
||||||
|
"celebrateLaborDay": "今天是勞動節,{}。",
|
||||||
|
"celebrateMotherDay": "今天是母親節,{}。",
|
||||||
|
"celebrateChildrenDay": "今天是兒童節,{}!",
|
||||||
|
"celebrateFatherDay": "今天是父親節,{}。",
|
||||||
|
"celebrateHalloween": "快樂在聖誕節,{}!",
|
||||||
|
"celebrateThanksgiving": "今天是感恩節,{}!",
|
||||||
|
"pendingBirthday": "{} 過生日",
|
||||||
|
"pendingMerryXmas": "{} 過聖誕節",
|
||||||
|
"pendingNewYear": "{} 跨年",
|
||||||
|
"pendingValentineDay": "{} 過情人節",
|
||||||
|
"pendingLaborDay": "{} 過勞動節",
|
||||||
|
"pendingMotherDay": "{} 過母親節",
|
||||||
|
"pendingChildrenDay": "{} 過兒童節",
|
||||||
|
"pendingFatherDay": "{} 過父親節",
|
||||||
|
"pendingHalloween": "{} 過聖誕節",
|
||||||
|
"pendingThanksgiving": "{} 過感恩節",
|
||||||
"friendNew": "新增好友",
|
"friendNew": "新增好友",
|
||||||
"friendRequests": "好友請求",
|
"friendRequests": "好友請求",
|
||||||
"friendRequestsDescription": {
|
"friendRequestsDescription": {
|
||||||
@ -399,7 +434,7 @@
|
|||||||
"accountStatus": "狀態",
|
"accountStatus": "狀態",
|
||||||
"accountStatusOnline": "線上",
|
"accountStatusOnline": "線上",
|
||||||
"accountStatusOffline": "離線",
|
"accountStatusOffline": "離線",
|
||||||
"accountStatusLastSeen": "最後一次在 {} 上線",
|
"accountStatusLastSeen": "最後一次上線於 {}",
|
||||||
"postArticle": "Solar Network 上的文章",
|
"postArticle": "Solar Network 上的文章",
|
||||||
"postStory": "Solar Network 上的故事",
|
"postStory": "Solar Network 上的故事",
|
||||||
"articleWrittenAt": "發表於 {}",
|
"articleWrittenAt": "發表於 {}",
|
||||||
@ -441,5 +476,33 @@
|
|||||||
"postImageShareReadMore": "掃描右側 QRCode 檢視全文",
|
"postImageShareReadMore": "掃描右側 QRCode 檢視全文",
|
||||||
"postImageShareAds": "來 Solar Network 探索更多有趣帖子",
|
"postImageShareAds": "來 Solar Network 探索更多有趣帖子",
|
||||||
"postShare": "分享",
|
"postShare": "分享",
|
||||||
"postShareImage": "分享帖圖"
|
"postShareImage": "分享帖圖",
|
||||||
|
"appInitializing": "正在初始化",
|
||||||
|
"poweredBy": "由 {} 提供支援",
|
||||||
|
"shareIntent": "分享",
|
||||||
|
"shareIntentDescription": "您想對您分享的內容做些什麼?",
|
||||||
|
"shareIntentPostStory": "釋出動態",
|
||||||
|
"updateAvailable": "檢測到更新可用",
|
||||||
|
"updateOngoing": "正在更新,請稍後……",
|
||||||
|
"custom": "自定義",
|
||||||
|
"colorSchemeIndigo": "靛藍",
|
||||||
|
"colorSchemeBlue": "藍色",
|
||||||
|
"colorSchemeGreen": "綠色",
|
||||||
|
"colorSchemeYellow": "黃色",
|
||||||
|
"colorSchemeOrange": "橙色",
|
||||||
|
"colorSchemeRed": "紅色",
|
||||||
|
"colorSchemeWhite": "白色",
|
||||||
|
"colorSchemeBlack": "黑色",
|
||||||
|
"colorSchemeApplied": "主題色已應用,可能需要重啟來生效。",
|
||||||
|
"postCategoryTechnology": "技術",
|
||||||
|
"postCategoryGaming": "遊戲",
|
||||||
|
"postCategoryLife": "生活",
|
||||||
|
"postCategoryArts": "藝術",
|
||||||
|
"postCategorySports": "體育",
|
||||||
|
"postCategoryMusic": "音樂",
|
||||||
|
"postCategoryNews": "新聞",
|
||||||
|
"postCategoryKnowledge": "知識",
|
||||||
|
"postCategoryLiterature": "文學",
|
||||||
|
"postCategoryFunny": "搞笑",
|
||||||
|
"postCategoryUncategorized": "未分類"
|
||||||
}
|
}
|
||||||
|
22
ios/Podfile
@ -35,6 +35,28 @@ target 'Runner' do
|
|||||||
target 'RunnerTests' do
|
target 'RunnerTests' do
|
||||||
inherit! :search_paths
|
inherit! :search_paths
|
||||||
end
|
end
|
||||||
|
|
||||||
|
target 'SolarNotifyService' do
|
||||||
|
inherit! :search_paths
|
||||||
|
pod 'home_widget', :path => '.symlinks/plugins/home_widget/ios'
|
||||||
|
|
||||||
|
pod 'Kingfisher', '~> 8.0'
|
||||||
|
pod 'Alamofire'
|
||||||
|
end
|
||||||
|
|
||||||
|
target 'SolarWidgetExtension' do
|
||||||
|
inherit! :search_paths
|
||||||
|
use_frameworks!
|
||||||
|
use_modular_headers!
|
||||||
|
|
||||||
|
pod 'home_widget', :path => '.symlinks/plugins/home_widget/ios'
|
||||||
|
|
||||||
|
pod 'Kingfisher', '~> 8.0'
|
||||||
|
end
|
||||||
|
|
||||||
|
target 'SolarShare' do
|
||||||
|
inherit! :search_paths
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
post_install do |installer|
|
post_install do |installer|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
PODS:
|
PODS:
|
||||||
|
- Alamofire (5.10.2)
|
||||||
- connectivity_plus (0.0.1):
|
- connectivity_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
@ -56,7 +57,7 @@ PODS:
|
|||||||
- Firebase/Analytics (= 11.4.0)
|
- Firebase/Analytics (= 11.4.0)
|
||||||
- firebase_core
|
- firebase_core
|
||||||
- Flutter
|
- Flutter
|
||||||
- firebase_core (3.8.1):
|
- firebase_core (3.9.0):
|
||||||
- Firebase/CoreOnly (= 11.4.0)
|
- Firebase/CoreOnly (= 11.4.0)
|
||||||
- Flutter
|
- Flutter
|
||||||
- firebase_messaging (15.1.6):
|
- firebase_messaging (15.1.6):
|
||||||
@ -102,6 +103,8 @@ PODS:
|
|||||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
- GoogleUtilities/UserDefaults (~> 8.0)
|
||||||
- nanopb (~> 3.30910.0)
|
- nanopb (~> 3.30910.0)
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
|
- flutter_app_update (0.0.1):
|
||||||
|
- Flutter
|
||||||
- flutter_native_splash (2.4.3):
|
- flutter_native_splash (2.4.3):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_udid (0.0.1):
|
- flutter_udid (0.0.1):
|
||||||
@ -163,8 +166,13 @@ PODS:
|
|||||||
- GoogleUtilities/UserDefaults (8.0.2):
|
- GoogleUtilities/UserDefaults (8.0.2):
|
||||||
- GoogleUtilities/Logger
|
- GoogleUtilities/Logger
|
||||||
- GoogleUtilities/Privacy
|
- GoogleUtilities/Privacy
|
||||||
|
- home_widget (0.0.1):
|
||||||
|
- Flutter
|
||||||
- image_picker_ios (0.0.1):
|
- image_picker_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- in_app_review (2.0.0):
|
||||||
|
- Flutter
|
||||||
|
- Kingfisher (8.1.3)
|
||||||
- livekit_client (2.3.2):
|
- livekit_client (2.3.2):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_webrtc
|
- flutter_webrtc
|
||||||
@ -190,6 +198,8 @@ PODS:
|
|||||||
- permission_handler_apple (9.3.0):
|
- permission_handler_apple (9.3.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- PromisesObjC (2.4.0)
|
- PromisesObjC (2.4.0)
|
||||||
|
- receive_sharing_intent (1.8.1):
|
||||||
|
- Flutter
|
||||||
- SAMKeychain (1.5.3)
|
- SAMKeychain (1.5.3)
|
||||||
- screen_brightness_ios (0.1.0):
|
- screen_brightness_ios (0.1.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
@ -212,8 +222,11 @@ PODS:
|
|||||||
- wakelock_plus (0.0.1):
|
- wakelock_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- WebRTC-SDK (125.6422.06)
|
- WebRTC-SDK (125.6422.06)
|
||||||
|
- workmanager (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
|
- Alamofire
|
||||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`)
|
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`)
|
||||||
- croppy (from `.symlinks/plugins/croppy/ios`)
|
- croppy (from `.symlinks/plugins/croppy/ios`)
|
||||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||||
@ -223,11 +236,15 @@ DEPENDENCIES:
|
|||||||
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||||
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
|
- flutter_app_update (from `.symlinks/plugins/flutter_app_update/ios`)
|
||||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||||
- flutter_udid (from `.symlinks/plugins/flutter_udid/ios`)
|
- flutter_udid (from `.symlinks/plugins/flutter_udid/ios`)
|
||||||
- flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`)
|
- flutter_webrtc (from `.symlinks/plugins/flutter_webrtc/ios`)
|
||||||
- gal (from `.symlinks/plugins/gal/darwin`)
|
- gal (from `.symlinks/plugins/gal/darwin`)
|
||||||
|
- home_widget (from `.symlinks/plugins/home_widget/ios`)
|
||||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||||
|
- in_app_review (from `.symlinks/plugins/in_app_review/ios`)
|
||||||
|
- Kingfisher (~> 8.0)
|
||||||
- livekit_client (from `.symlinks/plugins/livekit_client/ios`)
|
- livekit_client (from `.symlinks/plugins/livekit_client/ios`)
|
||||||
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
|
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
|
||||||
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
|
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
|
||||||
@ -236,6 +253,7 @@ DEPENDENCIES:
|
|||||||
- pasteboard (from `.symlinks/plugins/pasteboard/ios`)
|
- pasteboard (from `.symlinks/plugins/pasteboard/ios`)
|
||||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||||
|
- receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
|
||||||
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
|
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
@ -243,9 +261,11 @@ DEPENDENCIES:
|
|||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
|
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
|
||||||
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
||||||
|
- workmanager (from `.symlinks/plugins/workmanager/ios`)
|
||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
trunk:
|
trunk:
|
||||||
|
- Alamofire
|
||||||
- DKImagePickerController
|
- DKImagePickerController
|
||||||
- DKPhotoGallery
|
- DKPhotoGallery
|
||||||
- Firebase
|
- Firebase
|
||||||
@ -257,6 +277,7 @@ SPEC REPOS:
|
|||||||
- GoogleAppMeasurement
|
- GoogleAppMeasurement
|
||||||
- GoogleDataTransport
|
- GoogleDataTransport
|
||||||
- GoogleUtilities
|
- GoogleUtilities
|
||||||
|
- Kingfisher
|
||||||
- nanopb
|
- nanopb
|
||||||
- PromisesObjC
|
- PromisesObjC
|
||||||
- SAMKeychain
|
- SAMKeychain
|
||||||
@ -283,6 +304,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/firebase_messaging/ios"
|
:path: ".symlinks/plugins/firebase_messaging/ios"
|
||||||
Flutter:
|
Flutter:
|
||||||
:path: Flutter
|
:path: Flutter
|
||||||
|
flutter_app_update:
|
||||||
|
:path: ".symlinks/plugins/flutter_app_update/ios"
|
||||||
flutter_native_splash:
|
flutter_native_splash:
|
||||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||||
flutter_udid:
|
flutter_udid:
|
||||||
@ -291,8 +314,12 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/flutter_webrtc/ios"
|
:path: ".symlinks/plugins/flutter_webrtc/ios"
|
||||||
gal:
|
gal:
|
||||||
:path: ".symlinks/plugins/gal/darwin"
|
:path: ".symlinks/plugins/gal/darwin"
|
||||||
|
home_widget:
|
||||||
|
:path: ".symlinks/plugins/home_widget/ios"
|
||||||
image_picker_ios:
|
image_picker_ios:
|
||||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||||
|
in_app_review:
|
||||||
|
:path: ".symlinks/plugins/in_app_review/ios"
|
||||||
livekit_client:
|
livekit_client:
|
||||||
:path: ".symlinks/plugins/livekit_client/ios"
|
:path: ".symlinks/plugins/livekit_client/ios"
|
||||||
media_kit_libs_ios_video:
|
media_kit_libs_ios_video:
|
||||||
@ -309,6 +336,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||||
permission_handler_apple:
|
permission_handler_apple:
|
||||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||||
|
receive_sharing_intent:
|
||||||
|
:path: ".symlinks/plugins/receive_sharing_intent/ios"
|
||||||
screen_brightness_ios:
|
screen_brightness_ios:
|
||||||
:path: ".symlinks/plugins/screen_brightness_ios/ios"
|
:path: ".symlinks/plugins/screen_brightness_ios/ios"
|
||||||
share_plus:
|
share_plus:
|
||||||
@ -323,8 +352,11 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/volume_controller/ios"
|
:path: ".symlinks/plugins/volume_controller/ios"
|
||||||
wakelock_plus:
|
wakelock_plus:
|
||||||
:path: ".symlinks/plugins/wakelock_plus/ios"
|
:path: ".symlinks/plugins/wakelock_plus/ios"
|
||||||
|
workmanager:
|
||||||
|
:path: ".symlinks/plugins/workmanager/ios"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
|
Alamofire: 7193b3b92c74a07f85569e1a6c4f4237291e7496
|
||||||
connectivity_plus: 18382e7311ba19efcaee94442b23b32507b20695
|
connectivity_plus: 18382e7311ba19efcaee94442b23b32507b20695
|
||||||
croppy: b6199bc8d56bd2e03cc11609d1c47ad9875c1321
|
croppy: b6199bc8d56bd2e03cc11609d1c47ad9875c1321
|
||||||
device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342
|
device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342
|
||||||
@ -334,7 +366,7 @@ SPEC CHECKSUMS:
|
|||||||
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
|
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
|
||||||
Firebase: cf1b19f21410b029b6786a54e9764a0cacad3c99
|
Firebase: cf1b19f21410b029b6786a54e9764a0cacad3c99
|
||||||
firebase_analytics: 2815af29d49c1a994652abd37a5b001a88bc7b75
|
firebase_analytics: 2815af29d49c1a994652abd37a5b001a88bc7b75
|
||||||
firebase_core: 418aed674e9a0b8b6088aec16cde82a811f6261f
|
firebase_core: b62a5080210edad3f2934314a8b2c6f5124e8e10
|
||||||
firebase_messaging: 98619a0572d82cfb3668e78859ba9f1110e268c9
|
firebase_messaging: 98619a0572d82cfb3668e78859ba9f1110e268c9
|
||||||
FirebaseAnalytics: 3feef9ae8733c567866342a1000691baaa7cad49
|
FirebaseAnalytics: 3feef9ae8733c567866342a1000691baaa7cad49
|
||||||
FirebaseCore: e0510f1523bc0eb21653cac00792e1e2bd6f1771
|
FirebaseCore: e0510f1523bc0eb21653cac00792e1e2bd6f1771
|
||||||
@ -342,14 +374,18 @@ SPEC CHECKSUMS:
|
|||||||
FirebaseInstallations: 6ef4a1c7eb2a61ee1f74727d7f6ce2e72acf1414
|
FirebaseInstallations: 6ef4a1c7eb2a61ee1f74727d7f6ce2e72acf1414
|
||||||
FirebaseMessaging: f8a160d99c2c2e5babbbcc90c4a3e15db036aee2
|
FirebaseMessaging: f8a160d99c2c2e5babbbcc90c4a3e15db036aee2
|
||||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||||
|
flutter_app_update: 65f61da626cb111d1b24674abc4b01728d7723bc
|
||||||
flutter_native_splash: e8a1e01082d97a8099d973f919f57904c925008a
|
flutter_native_splash: e8a1e01082d97a8099d973f919f57904c925008a
|
||||||
flutter_udid: a2482c67a61b9c806ef59dd82ed8d007f1b7ac04
|
flutter_udid: b2417673f287ee62817a1de3d1643f47b9f508ab
|
||||||
flutter_webrtc: 1a53bd24f97bcfeff512f13699e721897f261563
|
flutter_webrtc: 1a53bd24f97bcfeff512f13699e721897f261563
|
||||||
gal: 61e868295d28fe67ffa297fae6dacebf56fd53e1
|
gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5
|
||||||
GoogleAppMeasurement: 987769c4ca6b968f2479fbcc9fe3ce34af454b8e
|
GoogleAppMeasurement: 987769c4ca6b968f2479fbcc9fe3ce34af454b8e
|
||||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||||
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
|
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
|
||||||
|
home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57
|
||||||
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
|
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
|
||||||
|
in_app_review: a31b5257259646ea78e0e35fc914979b0031d011
|
||||||
|
Kingfisher: f2af9028b16baf9dc6c07c570072bc41cbf009ef
|
||||||
livekit_client: 6108dad8b77db3142bafd4c630f471d0a54335cd
|
livekit_client: 6108dad8b77db3142bafd4c630f471d0a54335cd
|
||||||
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
||||||
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
||||||
@ -360,6 +396,7 @@ SPEC CHECKSUMS:
|
|||||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||||
|
receive_sharing_intent: 79c848f5b045674ad60b9fea3bafea59962ad2c1
|
||||||
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
||||||
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
||||||
SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8
|
SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8
|
||||||
@ -369,9 +406,10 @@ SPEC CHECKSUMS:
|
|||||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||||
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
||||||
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
||||||
wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1
|
wakelock_plus: 373cfe59b235a6dd5837d0fb88791d2f13a90d56
|
||||||
WebRTC-SDK: 79942c006ea64f6fb48d7da8a4786dfc820bc1db
|
WebRTC-SDK: 79942c006ea64f6fb48d7da8a4786dfc820bc1db
|
||||||
|
workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6
|
||||||
|
|
||||||
PODFILE CHECKSUM: d2bdaa1cc7915e14cf47235c34a21fcb07b00390
|
PODFILE CHECKSUM: 9b244e02f87527430136c8d21cbdcf1cd586b6bc
|
||||||
|
|
||||||
COCOAPODS: 1.16.2
|
COCOAPODS: 1.16.2
|
||||||
|
@ -11,6 +11,11 @@
|
|||||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
|
738C1EAC2D0D76A400A215F3 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 731B7B6B2D0D6CE000CEB9B7 /* WidgetKit.framework */; };
|
||||||
|
738C1EAD2D0D76A400A215F3 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 731B7B6D2D0D6CE000CEB9B7 /* SwiftUI.framework */; };
|
||||||
|
738C1EB82D0D76A500A215F3 /* SolarWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 738C1EAB2D0D76A400A215F3 /* SolarWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
|
7396A3522D16BD890095F4A8 /* NotifyDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7396A3512D16BD890095F4A8 /* NotifyDelegate.swift */; };
|
||||||
|
73B7746E2D0E869200A789CE /* SolarShare.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 73B774642D0E869200A789CE /* SolarShare.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
73DA8A012D05C7620024A03E /* SolarNotifyService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 73DA89FA2D05C7620024A03E /* SolarNotifyService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
73DA8A012D05C7620024A03E /* SolarNotifyService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 73DA89FA2D05C7620024A03E /* SolarNotifyService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||||
8CD0929C27BC410DD5056EAB /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = A2C24C5238FAC44EA2CCF738 /* GoogleService-Info.plist */; };
|
8CD0929C27BC410DD5056EAB /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = A2C24C5238FAC44EA2CCF738 /* GoogleService-Info.plist */; };
|
||||||
@ -18,6 +23,9 @@
|
|||||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
CED170BFB6A72CDDAC285637 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EDF483E994343CDFBF9BA347 /* Pods_Runner.framework */; };
|
CED170BFB6A72CDDAC285637 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EDF483E994343CDFBF9BA347 /* Pods_Runner.framework */; };
|
||||||
|
D5125CF12F159F0B8BC7641D /* Pods_SolarNotifyService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02469D286F48D84300484B1E /* Pods_SolarNotifyService.framework */; };
|
||||||
|
D962B51F682FBDEC00AC7281 /* Pods_SolarWidgetExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7B1A159F5551E280D0EFC129 /* Pods_SolarWidgetExtension.framework */; };
|
||||||
|
F51C4E3C8FA95426C91FC0A4 /* Pods_SolarShare.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 16F41E029731EA30268EDE2A /* Pods_SolarShare.framework */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@ -28,6 +36,20 @@
|
|||||||
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
|
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
|
||||||
remoteInfo = Runner;
|
remoteInfo = Runner;
|
||||||
};
|
};
|
||||||
|
738C1EB62D0D76A500A215F3 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 738C1EAA2D0D76A400A215F3;
|
||||||
|
remoteInfo = SolarWidgetExtension;
|
||||||
|
};
|
||||||
|
73B7746C2D0E869200A789CE /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 73B774632D0E869200A789CE;
|
||||||
|
remoteInfo = SolarShare;
|
||||||
|
};
|
||||||
73DA89FF2D05C7620024A03E /* PBXContainerItemProxy */ = {
|
73DA89FF2D05C7620024A03E /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
@ -40,10 +62,12 @@
|
|||||||
/* Begin PBXCopyFilesBuildPhase section */
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
73DA8A022D05C7620024A03E /* Embed Foundation Extensions */ = {
|
73DA8A022D05C7620024A03E /* Embed Foundation Extensions */ = {
|
||||||
isa = PBXCopyFilesBuildPhase;
|
isa = PBXCopyFilesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 12;
|
||||||
dstPath = "";
|
dstPath = "";
|
||||||
dstSubfolderSpec = 13;
|
dstSubfolderSpec = 13;
|
||||||
files = (
|
files = (
|
||||||
|
738C1EB82D0D76A500A215F3 /* SolarWidgetExtension.appex in Embed Foundation Extensions */,
|
||||||
|
73B7746E2D0E869200A789CE /* SolarShare.appex in Embed Foundation Extensions */,
|
||||||
73DA8A012D05C7620024A03E /* SolarNotifyService.appex in Embed Foundation Extensions */,
|
73DA8A012D05C7620024A03E /* SolarNotifyService.appex in Embed Foundation Extensions */,
|
||||||
);
|
);
|
||||||
name = "Embed Foundation Extensions";
|
name = "Embed Foundation Extensions";
|
||||||
@ -62,22 +86,40 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
02469D286F48D84300484B1E /* Pods_SolarNotifyService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SolarNotifyService.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
1077EFD9ACF793E9DA5D5B63 /* Pods-Runner-SolarNotifyService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner-SolarNotifyService.release.xcconfig"; path = "Target Support Files/Pods-Runner-SolarNotifyService/Pods-Runner-SolarNotifyService.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
|
16F41E029731EA30268EDE2A /* Pods_SolarShare.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SolarShare.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
2134F3903A0E8EB8CC2670BE /* Pods-SolarWidgetExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolarWidgetExtension.debug.xcconfig"; path = "Target Support Files/Pods-SolarWidgetExtension/Pods-SolarWidgetExtension.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
26CC8DE2338798EAB472B62D /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
26CC8DE2338798EAB472B62D /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
2DA1B873D39B9FD33298BBCE /* Pods-SolarShare.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolarShare.profile.xcconfig"; path = "Target Support Files/Pods-SolarShare/Pods-SolarShare.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
40B53769EB464E54DACA7CE4 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
40B53769EB464E54DACA7CE4 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
430F31F96B82659CBEAD4326 /* Pods-Runner-SolarWidgetExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner-SolarWidgetExtension.profile.xcconfig"; path = "Target Support Files/Pods-Runner-SolarWidgetExtension/Pods-Runner-SolarWidgetExtension.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
48AE73F9950AF4FB02B5E9F4 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
48AE73F9950AF4FB02B5E9F4 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
4A2F84B6033057E3BD2C7CB8 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
4A2F84B6033057E3BD2C7CB8 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
4CBF45ABD292EE527D0A4D1E /* Pods-SolarNotifyService.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolarNotifyService.profile.xcconfig"; path = "Target Support Files/Pods-SolarNotifyService/Pods-SolarNotifyService.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
5922A50B1231B06B92E31F20 /* Pods-SolarShare.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolarShare.debug.xcconfig"; path = "Target Support Files/Pods-SolarShare/Pods-SolarShare.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
64FBE78F9C282712818D6D95 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
64FBE78F9C282712818D6D95 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
6618E2E3015264643175B43D /* Pods-SolarWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolarWidgetExtension.release.xcconfig"; path = "Target Support Files/Pods-SolarWidgetExtension/Pods-SolarWidgetExtension.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
72E9279EFA6DAC00BBAC493C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
72E9279EFA6DAC00BBAC493C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
73111C212CEE3D5E004CF4B3 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
|
73111C212CEE3D5E004CF4B3 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
|
||||||
|
731B7B6B2D0D6CE000CEB9B7 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
|
||||||
|
731B7B6D2D0D6CE000CEB9B7 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
|
||||||
|
738C1EAB2D0D76A400A215F3 /* SolarWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SolarWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
738C1F132D0D7DDC00A215F3 /* SolarWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SolarWidgetExtension.entitlements; sourceTree = "<group>"; };
|
||||||
|
7396A3512D16BD890095F4A8 /* NotifyDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotifyDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
73B774642D0E869200A789CE /* SolarShare.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SolarShare.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
73DA89FA2D05C7620024A03E /* SolarNotifyService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SolarNotifyService.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
73DA89FA2D05C7620024A03E /* SolarNotifyService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SolarNotifyService.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
7B1A159F5551E280D0EFC129 /* Pods_SolarWidgetExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SolarWidgetExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
8E44A071621D5CAF864FB2F1 /* Pods-Runner-SolarNotifyService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner-SolarNotifyService.debug.xcconfig"; path = "Target Support Files/Pods-Runner-SolarNotifyService/Pods-Runner-SolarNotifyService.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
931FBE9EDB99B3AD8B1FFB00 /* Pods-Runner-SolarWidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner-SolarWidgetExtension.release.xcconfig"; path = "Target Support Files/Pods-Runner-SolarWidgetExtension/Pods-Runner-SolarWidgetExtension.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
96081771773FA019A97CCC3F /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
96081771773FA019A97CCC3F /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||||
@ -87,11 +129,53 @@
|
|||||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
A2C24C5238FAC44EA2CCF738 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
|
A2C24C5238FAC44EA2CCF738 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||||
|
B1763F1D7318A2745CA7EDFE /* Pods-SolarShare.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolarShare.release.xcconfig"; path = "Target Support Files/Pods-SolarShare/Pods-SolarShare.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
B4550C68292419CDC580808B /* Pods-Runner-SolarNotifyService.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner-SolarNotifyService.profile.xcconfig"; path = "Target Support Files/Pods-Runner-SolarNotifyService/Pods-Runner-SolarNotifyService.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
BCE0C4086B776A27B202B373 /* Pods-SolarWidgetExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolarWidgetExtension.profile.xcconfig"; path = "Target Support Files/Pods-SolarWidgetExtension/Pods-SolarWidgetExtension.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
BFF3B436D74FA8CBFFE34A27 /* Pods-Runner-SolarWidgetExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner-SolarWidgetExtension.debug.xcconfig"; path = "Target Support Files/Pods-Runner-SolarWidgetExtension/Pods-Runner-SolarWidgetExtension.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
D7E1FA77FDA53439DB2C0E75 /* Pods-SolarNotifyService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolarNotifyService.release.xcconfig"; path = "Target Support Files/Pods-SolarNotifyService/Pods-SolarNotifyService.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
D96D1DB4ED46A2640C1B9D34 /* Pods-SolarNotifyService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SolarNotifyService.debug.xcconfig"; path = "Target Support Files/Pods-SolarNotifyService/Pods-SolarNotifyService.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
EDF483E994343CDFBF9BA347 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
EDF483E994343CDFBF9BA347 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
73DA8A062D05C7620024A03E /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
|
738C1EB92D0D76A500A215F3 /* Exceptions for "SolarWidget" folder in "SolarWidgetExtension" target */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
|
membershipExceptions = (
|
||||||
|
Info.plist,
|
||||||
|
);
|
||||||
|
target = 738C1EAA2D0D76A400A215F3 /* SolarWidgetExtension */;
|
||||||
|
};
|
||||||
|
738C1F512D0D91D000A215F3 /* Exceptions for "Data" folder in "SolarWidgetExtension" target */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
|
membershipExceptions = (
|
||||||
|
Post.swift,
|
||||||
|
User.swift,
|
||||||
|
);
|
||||||
|
target = 738C1EAA2D0D76A400A215F3 /* SolarWidgetExtension */;
|
||||||
|
};
|
||||||
|
73B774722D0E869200A789CE /* Exceptions for "SolarShare" folder in "SolarShare" target */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
|
membershipExceptions = (
|
||||||
|
Info.plist,
|
||||||
|
);
|
||||||
|
target = 73B774632D0E869200A789CE /* SolarShare */;
|
||||||
|
};
|
||||||
|
73BC73712D0DDF6300956BE0 /* Exceptions for "Service" folder in "SolarNotifyService" target */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
|
membershipExceptions = (
|
||||||
|
Attachment.swift,
|
||||||
|
);
|
||||||
|
target = 73DA89F92D05C7620024A03E /* SolarNotifyService */;
|
||||||
|
};
|
||||||
|
73BC73722D0DDF6300956BE0 /* Exceptions for "Service" folder in "SolarWidgetExtension" target */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
|
membershipExceptions = (
|
||||||
|
Attachment.swift,
|
||||||
|
);
|
||||||
|
target = 738C1EAA2D0D76A400A215F3 /* SolarWidgetExtension */;
|
||||||
|
};
|
||||||
|
73DA8A062D05C7620024A03E /* Exceptions for "SolarNotifyService" folder in "SolarNotifyService" target */ = {
|
||||||
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
|
||||||
membershipExceptions = (
|
membershipExceptions = (
|
||||||
Info.plist,
|
Info.plist,
|
||||||
@ -101,14 +185,73 @@
|
|||||||
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||||
73DA89FB2D05C7620024A03E /* SolarNotifyService */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (73DA8A062D05C7620024A03E /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = SolarNotifyService; sourceTree = "<group>"; };
|
738C1EAE2D0D76A400A215F3 /* SolarWidget */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
738C1EB92D0D76A500A215F3 /* Exceptions for "SolarWidget" folder in "SolarWidgetExtension" target */,
|
||||||
|
);
|
||||||
|
path = SolarWidget;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
738C1F4F2D0D91CC00A215F3 /* Data */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
738C1F512D0D91D000A215F3 /* Exceptions for "Data" folder in "SolarWidgetExtension" target */,
|
||||||
|
);
|
||||||
|
path = Data;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
73B774652D0E869200A789CE /* SolarShare */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
73B774722D0E869200A789CE /* Exceptions for "SolarShare" folder in "SolarShare" target */,
|
||||||
|
);
|
||||||
|
path = SolarShare;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
73BC736C2D0DDF5600956BE0 /* Service */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
73BC73712D0DDF6300956BE0 /* Exceptions for "Service" folder in "SolarNotifyService" target */,
|
||||||
|
73BC73722D0DDF6300956BE0 /* Exceptions for "Service" folder in "SolarWidgetExtension" target */,
|
||||||
|
);
|
||||||
|
path = Service;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
73DA89FB2D05C7620024A03E /* SolarNotifyService */ = {
|
||||||
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
73DA8A062D05C7620024A03E /* Exceptions for "SolarNotifyService" folder in "SolarNotifyService" target */,
|
||||||
|
);
|
||||||
|
path = SolarNotifyService;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
738C1EA82D0D76A400A215F3 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
738C1EAD2D0D76A400A215F3 /* SwiftUI.framework in Frameworks */,
|
||||||
|
738C1EAC2D0D76A400A215F3 /* WidgetKit.framework in Frameworks */,
|
||||||
|
D962B51F682FBDEC00AC7281 /* Pods_SolarWidgetExtension.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
73B774612D0E869200A789CE /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
F51C4E3C8FA95426C91FC0A4 /* Pods_SolarShare.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
73DA89F72D05C7620024A03E /* Frameworks */ = {
|
73DA89F72D05C7620024A03E /* Frameworks */ = {
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
D5125CF12F159F0B8BC7641D /* Pods_SolarNotifyService.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -136,6 +279,11 @@
|
|||||||
children = (
|
children = (
|
||||||
EDF483E994343CDFBF9BA347 /* Pods_Runner.framework */,
|
EDF483E994343CDFBF9BA347 /* Pods_Runner.framework */,
|
||||||
26CC8DE2338798EAB472B62D /* Pods_RunnerTests.framework */,
|
26CC8DE2338798EAB472B62D /* Pods_RunnerTests.framework */,
|
||||||
|
731B7B6B2D0D6CE000CEB9B7 /* WidgetKit.framework */,
|
||||||
|
731B7B6D2D0D6CE000CEB9B7 /* SwiftUI.framework */,
|
||||||
|
16F41E029731EA30268EDE2A /* Pods_SolarShare.framework */,
|
||||||
|
02469D286F48D84300484B1E /* Pods_SolarNotifyService.framework */,
|
||||||
|
7B1A159F5551E280D0EFC129 /* Pods_SolarWidgetExtension.framework */,
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -162,9 +310,12 @@
|
|||||||
97C146E51CF9000F007C117D = {
|
97C146E51CF9000F007C117D = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
738C1F132D0D7DDC00A215F3 /* SolarWidgetExtension.entitlements */,
|
||||||
9740EEB11CF90186004384FC /* Flutter */,
|
9740EEB11CF90186004384FC /* Flutter */,
|
||||||
97C146F01CF9000F007C117D /* Runner */,
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
73DA89FB2D05C7620024A03E /* SolarNotifyService */,
|
73DA89FB2D05C7620024A03E /* SolarNotifyService */,
|
||||||
|
738C1EAE2D0D76A400A215F3 /* SolarWidget */,
|
||||||
|
73B774652D0E869200A789CE /* SolarShare */,
|
||||||
97C146EF1CF9000F007C117D /* Products */,
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||||
F5165E3BD1F2519F85CD4BE2 /* Pods */,
|
F5165E3BD1F2519F85CD4BE2 /* Pods */,
|
||||||
@ -179,6 +330,8 @@
|
|||||||
97C146EE1CF9000F007C117D /* Runner.app */,
|
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
||||||
73DA89FA2D05C7620024A03E /* SolarNotifyService.appex */,
|
73DA89FA2D05C7620024A03E /* SolarNotifyService.appex */,
|
||||||
|
738C1EAB2D0D76A400A215F3 /* SolarWidgetExtension.appex */,
|
||||||
|
73B774642D0E869200A789CE /* SolarShare.appex */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -186,6 +339,8 @@
|
|||||||
97C146F01CF9000F007C117D /* Runner */ = {
|
97C146F01CF9000F007C117D /* Runner */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
73BC736C2D0DDF5600956BE0 /* Service */,
|
||||||
|
738C1F4F2D0D91CC00A215F3 /* Data */,
|
||||||
73111C212CEE3D5E004CF4B3 /* Runner.entitlements */,
|
73111C212CEE3D5E004CF4B3 /* Runner.entitlements */,
|
||||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||||
@ -195,6 +350,7 @@
|
|||||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||||
|
7396A3512D16BD890095F4A8 /* NotifyDelegate.swift */,
|
||||||
);
|
);
|
||||||
path = Runner;
|
path = Runner;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -208,6 +364,21 @@
|
|||||||
40B53769EB464E54DACA7CE4 /* Pods-RunnerTests.debug.xcconfig */,
|
40B53769EB464E54DACA7CE4 /* Pods-RunnerTests.debug.xcconfig */,
|
||||||
64FBE78F9C282712818D6D95 /* Pods-RunnerTests.release.xcconfig */,
|
64FBE78F9C282712818D6D95 /* Pods-RunnerTests.release.xcconfig */,
|
||||||
96081771773FA019A97CCC3F /* Pods-RunnerTests.profile.xcconfig */,
|
96081771773FA019A97CCC3F /* Pods-RunnerTests.profile.xcconfig */,
|
||||||
|
5922A50B1231B06B92E31F20 /* Pods-SolarShare.debug.xcconfig */,
|
||||||
|
B1763F1D7318A2745CA7EDFE /* Pods-SolarShare.release.xcconfig */,
|
||||||
|
2DA1B873D39B9FD33298BBCE /* Pods-SolarShare.profile.xcconfig */,
|
||||||
|
2134F3903A0E8EB8CC2670BE /* Pods-SolarWidgetExtension.debug.xcconfig */,
|
||||||
|
6618E2E3015264643175B43D /* Pods-SolarWidgetExtension.release.xcconfig */,
|
||||||
|
BCE0C4086B776A27B202B373 /* Pods-SolarWidgetExtension.profile.xcconfig */,
|
||||||
|
D96D1DB4ED46A2640C1B9D34 /* Pods-SolarNotifyService.debug.xcconfig */,
|
||||||
|
D7E1FA77FDA53439DB2C0E75 /* Pods-SolarNotifyService.release.xcconfig */,
|
||||||
|
4CBF45ABD292EE527D0A4D1E /* Pods-SolarNotifyService.profile.xcconfig */,
|
||||||
|
8E44A071621D5CAF864FB2F1 /* Pods-Runner-SolarNotifyService.debug.xcconfig */,
|
||||||
|
1077EFD9ACF793E9DA5D5B63 /* Pods-Runner-SolarNotifyService.release.xcconfig */,
|
||||||
|
B4550C68292419CDC580808B /* Pods-Runner-SolarNotifyService.profile.xcconfig */,
|
||||||
|
BFF3B436D74FA8CBFFE34A27 /* Pods-Runner-SolarWidgetExtension.debug.xcconfig */,
|
||||||
|
931FBE9EDB99B3AD8B1FFB00 /* Pods-Runner-SolarWidgetExtension.release.xcconfig */,
|
||||||
|
430F31F96B82659CBEAD4326 /* Pods-Runner-SolarWidgetExtension.profile.xcconfig */,
|
||||||
);
|
);
|
||||||
path = Pods;
|
path = Pods;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -234,10 +405,54 @@
|
|||||||
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
|
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
|
||||||
productType = "com.apple.product-type.bundle.unit-test";
|
productType = "com.apple.product-type.bundle.unit-test";
|
||||||
};
|
};
|
||||||
|
738C1EAA2D0D76A400A215F3 /* SolarWidgetExtension */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 738C1EBA2D0D76A500A215F3 /* Build configuration list for PBXNativeTarget "SolarWidgetExtension" */;
|
||||||
|
buildPhases = (
|
||||||
|
F2FCDA0E1BD434BF4883AFFD /* [CP] Check Pods Manifest.lock */,
|
||||||
|
738C1EA72D0D76A400A215F3 /* Sources */,
|
||||||
|
738C1EA82D0D76A400A215F3 /* Frameworks */,
|
||||||
|
738C1EA92D0D76A400A215F3 /* Resources */,
|
||||||
|
738C1EBE2D0D76C500A215F3 /* Copy Bundle Version */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
fileSystemSynchronizedGroups = (
|
||||||
|
738C1EAE2D0D76A400A215F3 /* SolarWidget */,
|
||||||
|
);
|
||||||
|
name = SolarWidgetExtension;
|
||||||
|
productName = SolarWidgetExtension;
|
||||||
|
productReference = 738C1EAB2D0D76A400A215F3 /* SolarWidgetExtension.appex */;
|
||||||
|
productType = "com.apple.product-type.app-extension";
|
||||||
|
};
|
||||||
|
73B774632D0E869200A789CE /* SolarShare */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 73B774732D0E869200A789CE /* Build configuration list for PBXNativeTarget "SolarShare" */;
|
||||||
|
buildPhases = (
|
||||||
|
9E6442609CE65E253572BC79 /* [CP] Check Pods Manifest.lock */,
|
||||||
|
73B774602D0E869200A789CE /* Sources */,
|
||||||
|
73B774612D0E869200A789CE /* Frameworks */,
|
||||||
|
73B774622D0E869200A789CE /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
fileSystemSynchronizedGroups = (
|
||||||
|
73B774652D0E869200A789CE /* SolarShare */,
|
||||||
|
);
|
||||||
|
name = SolarShare;
|
||||||
|
productName = SolarShare;
|
||||||
|
productReference = 73B774642D0E869200A789CE /* SolarShare.appex */;
|
||||||
|
productType = "com.apple.product-type.app-extension";
|
||||||
|
};
|
||||||
73DA89F92D05C7620024A03E /* SolarNotifyService */ = {
|
73DA89F92D05C7620024A03E /* SolarNotifyService */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 73DA8A072D05C7620024A03E /* Build configuration list for PBXNativeTarget "SolarNotifyService" */;
|
buildConfigurationList = 73DA8A072D05C7620024A03E /* Build configuration list for PBXNativeTarget "SolarNotifyService" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
|
50F5704AB2E7309C916CA2E7 /* [CP] Check Pods Manifest.lock */,
|
||||||
73DA89F62D05C7620024A03E /* Sources */,
|
73DA89F62D05C7620024A03E /* Sources */,
|
||||||
73DA89F72D05C7620024A03E /* Frameworks */,
|
73DA89F72D05C7620024A03E /* Frameworks */,
|
||||||
73DA89F82D05C7620024A03E /* Resources */,
|
73DA89F82D05C7620024A03E /* Resources */,
|
||||||
@ -250,8 +465,6 @@
|
|||||||
73DA89FB2D05C7620024A03E /* SolarNotifyService */,
|
73DA89FB2D05C7620024A03E /* SolarNotifyService */,
|
||||||
);
|
);
|
||||||
name = SolarNotifyService;
|
name = SolarNotifyService;
|
||||||
packageProductDependencies = (
|
|
||||||
);
|
|
||||||
productName = SolarNotifyService;
|
productName = SolarNotifyService;
|
||||||
productReference = 73DA89FA2D05C7620024A03E /* SolarNotifyService.appex */;
|
productReference = 73DA89FA2D05C7620024A03E /* SolarNotifyService.appex */;
|
||||||
productType = "com.apple.product-type.app-extension";
|
productType = "com.apple.product-type.app-extension";
|
||||||
@ -276,6 +489,12 @@
|
|||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
73DA8A002D05C7620024A03E /* PBXTargetDependency */,
|
73DA8A002D05C7620024A03E /* PBXTargetDependency */,
|
||||||
|
738C1EB72D0D76A500A215F3 /* PBXTargetDependency */,
|
||||||
|
73B7746D2D0E869200A789CE /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
fileSystemSynchronizedGroups = (
|
||||||
|
738C1F4F2D0D91CC00A215F3 /* Data */,
|
||||||
|
73BC736C2D0DDF5600956BE0 /* Service */,
|
||||||
);
|
);
|
||||||
name = Runner;
|
name = Runner;
|
||||||
productName = Runner;
|
productName = Runner;
|
||||||
@ -289,7 +508,7 @@
|
|||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
BuildIndependentTargetsInParallel = YES;
|
BuildIndependentTargetsInParallel = YES;
|
||||||
LastSwiftUpdateCheck = 1610;
|
LastSwiftUpdateCheck = 1620;
|
||||||
LastUpgradeCheck = 1510;
|
LastUpgradeCheck = 1510;
|
||||||
ORGANIZATIONNAME = "";
|
ORGANIZATIONNAME = "";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
@ -297,6 +516,12 @@
|
|||||||
CreatedOnToolsVersion = 14.0;
|
CreatedOnToolsVersion = 14.0;
|
||||||
TestTargetID = 97C146ED1CF9000F007C117D;
|
TestTargetID = 97C146ED1CF9000F007C117D;
|
||||||
};
|
};
|
||||||
|
738C1EAA2D0D76A400A215F3 = {
|
||||||
|
CreatedOnToolsVersion = 16.2;
|
||||||
|
};
|
||||||
|
73B774632D0E869200A789CE = {
|
||||||
|
CreatedOnToolsVersion = 16.2;
|
||||||
|
};
|
||||||
73DA89F92D05C7620024A03E = {
|
73DA89F92D05C7620024A03E = {
|
||||||
CreatedOnToolsVersion = 16.1;
|
CreatedOnToolsVersion = 16.1;
|
||||||
};
|
};
|
||||||
@ -307,7 +532,6 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||||
compatibilityVersion = "Xcode 9.3";
|
|
||||||
developmentRegion = en;
|
developmentRegion = en;
|
||||||
hasScannedForEncodings = 0;
|
hasScannedForEncodings = 0;
|
||||||
knownRegions = (
|
knownRegions = (
|
||||||
@ -315,6 +539,7 @@
|
|||||||
Base,
|
Base,
|
||||||
);
|
);
|
||||||
mainGroup = 97C146E51CF9000F007C117D;
|
mainGroup = 97C146E51CF9000F007C117D;
|
||||||
|
preferredProjectObjectVersion = 77;
|
||||||
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
@ -322,6 +547,8 @@
|
|||||||
97C146ED1CF9000F007C117D /* Runner */,
|
97C146ED1CF9000F007C117D /* Runner */,
|
||||||
331C8080294A63A400263BE5 /* RunnerTests */,
|
331C8080294A63A400263BE5 /* RunnerTests */,
|
||||||
73DA89F92D05C7620024A03E /* SolarNotifyService */,
|
73DA89F92D05C7620024A03E /* SolarNotifyService */,
|
||||||
|
738C1EAA2D0D76A400A215F3 /* SolarWidgetExtension */,
|
||||||
|
73B774632D0E869200A789CE /* SolarShare */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
@ -334,6 +561,20 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
738C1EA92D0D76A400A215F3 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
73B774622D0E869200A789CE /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
73DA89F82D05C7620024A03E /* Resources */ = {
|
73DA89F82D05C7620024A03E /* Resources */ = {
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@ -388,7 +629,7 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n";
|
||||||
};
|
};
|
||||||
43B5CF57FD79BC21654EE037 /* [CP] Copy Pods Resources */ = {
|
43B5CF57FD79BC21654EE037 /* [CP] Copy Pods Resources */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
@ -407,6 +648,46 @@
|
|||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||||
showEnvVarsInLog = 0;
|
showEnvVarsInLog = 0;
|
||||||
};
|
};
|
||||||
|
50F5704AB2E7309C916CA2E7 /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-SolarNotifyService-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
738C1EBE2D0D76C500A215F3 /* Copy Bundle Version */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "Copy Bundle Version";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "generatedPath=\"$SRCROOT/Flutter/Generated.xcconfig\"\n\n# Read and trim versionNumber and buildNumber\nversionNumber=$(grep FLUTTER_BUILD_NAME \"$generatedPath\" | cut -d '=' -f2 | xargs)\nbuildNumber=$(grep FLUTTER_BUILD_NUMBER \"$generatedPath\" | cut -d '=' -f2 | xargs)\n\ninfoPlistPath=\"$SRCROOT/HomeExampleWidget/Info.plist\"\n\n# Check and add CFBundleVersion if it does not exist\n/usr/libexec/PlistBuddy -c \"Print :CFBundleVersion\" \"$infoPlistPath\" 2>/dev/null\nif [ $? != 0 ]; then\n /usr/libexec/PlistBuddy -c \"Add :CFBundleVersion string $buildNumber\" \"$infoPlistPath\"\nelse\n /usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $buildNumber\" \"$infoPlistPath\"\nfi\n\n# Check and add CFBundleShortVersionString if it does not exist\n/usr/libexec/PlistBuddy -c \"Print :CFBundleShortVersionString\" \"$infoPlistPath\" 2>/dev/null\nif [ $? != 0 ]; then\n /usr/libexec/PlistBuddy -c \"Add :CFBundleShortVersionString string $versionNumber\" \"$infoPlistPath\"\nelse\n /usr/libexec/PlistBuddy -c \"Set :CFBundleShortVersionString $versionNumber\" \"$infoPlistPath\"\nfi\n\n";
|
||||||
|
};
|
||||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
alwaysOutOfDate = 1;
|
alwaysOutOfDate = 1;
|
||||||
@ -422,6 +703,28 @@
|
|||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||||
};
|
};
|
||||||
|
9E6442609CE65E253572BC79 /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-SolarShare-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
C431F2F1BD10FD03D14DDAE1 /* [CP] Check Pods Manifest.lock */ = {
|
C431F2F1BD10FD03D14DDAE1 /* [CP] Check Pods Manifest.lock */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@ -466,6 +769,28 @@
|
|||||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
showEnvVarsInLog = 0;
|
showEnvVarsInLog = 0;
|
||||||
};
|
};
|
||||||
|
F2FCDA0E1BD434BF4883AFFD /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-SolarWidgetExtension-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
FC4815D44D909666EB1FA614 /* [CP] Embed Pods Frameworks */ = {
|
FC4815D44D909666EB1FA614 /* [CP] Embed Pods Frameworks */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@ -494,6 +819,20 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
738C1EA72D0D76A400A215F3 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
73B774602D0E869200A789CE /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
73DA89F62D05C7620024A03E /* Sources */ = {
|
73DA89F62D05C7620024A03E /* Sources */ = {
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@ -507,6 +846,7 @@
|
|||||||
files = (
|
files = (
|
||||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||||
|
7396A3522D16BD890095F4A8 /* NotifyDelegate.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -518,6 +858,16 @@
|
|||||||
target = 97C146ED1CF9000F007C117D /* Runner */;
|
target = 97C146ED1CF9000F007C117D /* Runner */;
|
||||||
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
|
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
|
738C1EB72D0D76A500A215F3 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 738C1EAA2D0D76A400A215F3 /* SolarWidgetExtension */;
|
||||||
|
targetProxy = 738C1EB62D0D76A500A215F3 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
73B7746D2D0E869200A789CE /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 73B774632D0E869200A789CE /* SolarShare */;
|
||||||
|
targetProxy = 73B7746C2D0E869200A789CE /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
73DA8A002D05C7620024A03E /* PBXTargetDependency */ = {
|
73DA8A002D05C7620024A03E /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
target = 73DA89F92D05C7620024A03E /* SolarNotifyService */;
|
target = 73DA89F92D05C7620024A03E /* SolarNotifyService */;
|
||||||
@ -605,11 +955,13 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
CUSTOM_GROUP_ID = group.solsynth.solian;
|
||||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = Solian;
|
INFOPLIST_KEY_CFBundleDisplayName = Solian;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@ -629,6 +981,7 @@
|
|||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests;
|
||||||
@ -647,6 +1000,7 @@
|
|||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests;
|
||||||
@ -663,6 +1017,7 @@
|
|||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests;
|
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.RunnerTests;
|
||||||
@ -672,8 +1027,135 @@
|
|||||||
};
|
};
|
||||||
name = Profile;
|
name = Profile;
|
||||||
};
|
};
|
||||||
73DA8A032D05C7620024A03E /* Debug */ = {
|
738C1EBB2D0D76A500A215F3 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 2134F3903A0E8EB8CC2670BE /* Pods-SolarWidgetExtension.debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = SolarWidgetExtension.entitlements;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = SolarWidget/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = SolarWidget;
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolarWidget;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
738C1EBC2D0D76A500A215F3 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 6618E2E3015264643175B43D /* Pods-SolarWidgetExtension.release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = SolarWidgetExtension.entitlements;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = SolarWidget/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = SolarWidget;
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolarWidget;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
738C1EBD2D0D76A500A215F3 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = BCE0C4086B776A27B202B373 /* Pods-SolarWidgetExtension.profile.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = SolarWidgetExtension.entitlements;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = SolarWidget/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = SolarWidget;
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolarWidget;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
73B7746F2D0E869200A789CE /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 5922A50B1231B06B92E31F20 /* Pods-SolarShare.debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
@ -682,6 +1164,130 @@
|
|||||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = SolarShare/SolarShare.entitlements;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
CUSTOM_GROUP_ID = group.solsynth.solian;
|
||||||
|
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = SolarShare/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = SolarShare;
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolarShare;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
73B774702D0E869200A789CE /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = B1763F1D7318A2745CA7EDFE /* Pods-SolarShare.release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = SolarShare/SolarShare.entitlements;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
CUSTOM_GROUP_ID = group.solsynth.solian;
|
||||||
|
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = SolarShare/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = SolarShare;
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolarShare;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
73B774712D0E869200A789CE /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 2DA1B873D39B9FD33298BBCE /* Pods-SolarShare.profile.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = SolarShare/SolarShare.entitlements;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
CUSTOM_GROUP_ID = group.solsynth.solian;
|
||||||
|
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
INFOPLIST_FILE = SolarShare/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = SolarShare;
|
||||||
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
"@executable_path/../../Frameworks",
|
||||||
|
);
|
||||||
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = dev.solsynth.solian.SolarShare;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
73DA8A032D05C7620024A03E /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = D96D1DB4ED46A2640C1B9D34 /* Pods-SolarNotifyService.debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = SolarNotifyService/SolarNotifyService.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||||
@ -691,7 +1297,7 @@
|
|||||||
INFOPLIST_FILE = SolarNotifyService/Info.plist;
|
INFOPLIST_FILE = SolarNotifyService/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = SolarNotifyService;
|
INFOPLIST_KEY_CFBundleDisplayName = SolarNotifyService;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@ -714,6 +1320,7 @@
|
|||||||
};
|
};
|
||||||
73DA8A042D05C7620024A03E /* Release */ = {
|
73DA8A042D05C7620024A03E /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = D7E1FA77FDA53439DB2C0E75 /* Pods-SolarNotifyService.release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
@ -722,6 +1329,7 @@
|
|||||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = SolarNotifyService/SolarNotifyService.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||||
@ -731,7 +1339,7 @@
|
|||||||
INFOPLIST_FILE = SolarNotifyService/Info.plist;
|
INFOPLIST_FILE = SolarNotifyService/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = SolarNotifyService;
|
INFOPLIST_KEY_CFBundleDisplayName = SolarNotifyService;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@ -751,6 +1359,7 @@
|
|||||||
};
|
};
|
||||||
73DA8A052D05C7620024A03E /* Profile */ = {
|
73DA8A052D05C7620024A03E /* Profile */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 4CBF45ABD292EE527D0A4D1E /* Pods-SolarNotifyService.profile.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
@ -759,6 +1368,7 @@
|
|||||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = SolarNotifyService/SolarNotifyService.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||||
@ -768,7 +1378,7 @@
|
|||||||
INFOPLIST_FILE = SolarNotifyService/Info.plist;
|
INFOPLIST_FILE = SolarNotifyService/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = SolarNotifyService;
|
INFOPLIST_KEY_CFBundleDisplayName = SolarNotifyService;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@ -905,11 +1515,13 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
CUSTOM_GROUP_ID = group.solsynth.solian;
|
||||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = Solian;
|
INFOPLIST_KEY_CFBundleDisplayName = Solian;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@ -931,11 +1543,13 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
CUSTOM_GROUP_ID = group.solsynth.solian;
|
||||||
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
DEVELOPMENT_TEAM = W7HPZ53V6B;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = Solian;
|
INFOPLIST_KEY_CFBundleDisplayName = Solian;
|
||||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@ -961,6 +1575,26 @@
|
|||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
|
738C1EBA2D0D76A500A215F3 /* Build configuration list for PBXNativeTarget "SolarWidgetExtension" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
738C1EBB2D0D76A500A215F3 /* Debug */,
|
||||||
|
738C1EBC2D0D76A500A215F3 /* Release */,
|
||||||
|
738C1EBD2D0D76A500A215F3 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
73B774732D0E869200A789CE /* Build configuration list for PBXNativeTarget "SolarShare" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
73B7746F2D0E869200A789CE /* Debug */,
|
||||||
|
73B774702D0E869200A789CE /* Release */,
|
||||||
|
73B774712D0E869200A789CE /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
73DA8A072D05C7620024A03E /* Build configuration list for PBXNativeTarget "SolarNotifyService" */ = {
|
73DA8A072D05C7620024A03E /* Build configuration list for PBXNativeTarget "SolarNotifyService" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
|
@ -1,13 +1,26 @@
|
|||||||
import Flutter
|
import Flutter
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
import workmanager
|
||||||
|
|
||||||
@main
|
@main
|
||||||
@objc class AppDelegate: FlutterAppDelegate {
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
|
let notifyDelegate = NotifyDelegate()
|
||||||
|
|
||||||
override func application(
|
override func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
GeneratedPluginRegistrant.register(with: self)
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
|
WorkmanagerPlugin.setPluginRegistrantCallback { registry in
|
||||||
|
GeneratedPluginRegistrant.register(with: registry)
|
||||||
|
}
|
||||||
|
|
||||||
|
UIApplication.shared.setMinimumBackgroundFetchInterval(TimeInterval(60*5))
|
||||||
|
|
||||||
|
UNUserNotificationCenter.current().delegate = notifyDelegate
|
||||||
|
|
||||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
39
ios/Runner/AppIntent.swift
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
//
|
||||||
|
// AppIntent.swift
|
||||||
|
// Runner
|
||||||
|
//
|
||||||
|
// Created by LittleSheep on 2024/12/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import AppIntents
|
||||||
|
import Flutter
|
||||||
|
import Foundation
|
||||||
|
import home_widget
|
||||||
|
|
||||||
|
@available(iOS 17, *)
|
||||||
|
public struct AppBackgroundIntent: AppIntent {
|
||||||
|
static public var title: LocalizedStringResource = "Solar Network Background Intent"
|
||||||
|
|
||||||
|
@Parameter(title: "Widget URI")
|
||||||
|
var url: URL?
|
||||||
|
|
||||||
|
@Parameter(title: "AppGroup")
|
||||||
|
var appGroup: String?
|
||||||
|
|
||||||
|
public init() {}
|
||||||
|
|
||||||
|
public init(url: URL?, appGroup: String?) {
|
||||||
|
self.url = url
|
||||||
|
self.appGroup = appGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
public func perform() async throws -> some IntentResult {
|
||||||
|
await HomeWidgetBackgroundWorker.run(url: url, appGroup: appGroup!)
|
||||||
|
|
||||||
|
return .result()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 17, *)
|
||||||
|
@available(iOSApplicationExtension, unavailable)
|
||||||
|
extension AppBackgroundIntent: ForegroundContinuableIntent {}
|
38
ios/Runner/Data/Post.swift
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
//
|
||||||
|
// SolarPost.swift
|
||||||
|
// Runner
|
||||||
|
//
|
||||||
|
// Created by LittleSheep on 2024/12/14.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct SolarPost : Codable {
|
||||||
|
let id: Int
|
||||||
|
let body: SolarPostBody
|
||||||
|
let publisher: SolarPublisher
|
||||||
|
let publisherId: Int
|
||||||
|
let createdAt: Date
|
||||||
|
let updatedAt: Date
|
||||||
|
let editedAt: Date?
|
||||||
|
let publishedAt: Date?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SolarPostBody : Codable {
|
||||||
|
let content: String?
|
||||||
|
let title: String?
|
||||||
|
let description: String?
|
||||||
|
let attachments: [String]?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SolarPublisher : Codable {
|
||||||
|
let id: Int
|
||||||
|
let name: String
|
||||||
|
let nick: String
|
||||||
|
let description: String?
|
||||||
|
let avatar: String?
|
||||||
|
let banner: String?
|
||||||
|
let createdAt: Date
|
||||||
|
let updatedAt: Date
|
||||||
|
}
|
21
ios/Runner/Data/User.swift
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// SolarData.swift
|
||||||
|
// Runner
|
||||||
|
//
|
||||||
|
// Created by LittleSheep on 2024/12/14.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct SolarUser: Codable {
|
||||||
|
let id: Int
|
||||||
|
let name: String
|
||||||
|
let nick: String
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SolarCheckInRecord: Codable {
|
||||||
|
let id: Int
|
||||||
|
let resultTier: Int
|
||||||
|
let resultExperience: Int
|
||||||
|
let createdAt: Date
|
||||||
|
}
|
@ -2,6 +2,8 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>AppGroupId</key>
|
||||||
|
<string>group.solsynth.solian</string>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
@ -27,6 +29,17 @@
|
|||||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
@ -34,9 +47,9 @@
|
|||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
<string>Grant access to Photo Library will allow Solian take photo or video for your post.</string>
|
<string>Grant access to Camera will allow Solian take photo or video for your post.</string>
|
||||||
<key>NSMicrophoneUsageDescription</key>
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
<string>Grant access to Photo Library will allow Solian record audio for your post.</string>
|
<string>Grant access to Microphone will allow Solian record audio for your post.</string>
|
||||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||||
<string>Grant access to Photo Library will allow Solian download photo to album for you.</string>
|
<string>Grant access to Photo Library will allow Solian download photo to album for you.</string>
|
||||||
<key>NSPhotoLibraryUsageDescription</key>
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
|
55
ios/Runner/NotifyDelegate.swift
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
//
|
||||||
|
// NotifyDelegate.swift
|
||||||
|
// Runner
|
||||||
|
//
|
||||||
|
// Created by LittleSheep on 2024/12/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import home_widget
|
||||||
|
import Alamofire
|
||||||
|
|
||||||
|
class NotifyDelegate: UIResponder, UNUserNotificationCenterDelegate {
|
||||||
|
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
|
||||||
|
if let textResponse = response as? UNTextInputNotificationResponse {
|
||||||
|
let content = response.notification.request.content
|
||||||
|
guard let metadata = content.userInfo["metadata"] as? [AnyHashable: Any] else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let channelId = metadata["channel_id"] as? Int
|
||||||
|
let eventId = metadata["event_id"] as? Int
|
||||||
|
|
||||||
|
let replyToken = metadata["reply_token"] as? String
|
||||||
|
|
||||||
|
if (channelId == nil || eventId == nil || replyToken == nil) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let serverUrl = "https://api.sn.solsynth.dev"
|
||||||
|
let url = "\(serverUrl)/cgi/im/quick/\(channelId!)/reply/\(eventId!)?replyToken=\(replyToken!)"
|
||||||
|
|
||||||
|
let parameters: [String: Any] = [
|
||||||
|
"type": "messages.new",
|
||||||
|
"body": [
|
||||||
|
"text": textResponse.userText,
|
||||||
|
"algorithm": "plain"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default)
|
||||||
|
.validate()
|
||||||
|
.responseString { response in
|
||||||
|
switch response.result {
|
||||||
|
case .success(_):
|
||||||
|
break
|
||||||
|
case .failure(let error):
|
||||||
|
print("Failed to send chat reply message: \(error)")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
completionHandler()
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,16 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>aps-environment</key>
|
<key>aps-environment</key>
|
||||||
<string>development</string>
|
<string>development</string>
|
||||||
|
<key>com.apple.developer.associated-domains</key>
|
||||||
|
<array>
|
||||||
|
<string>webcredentials:sn.solsynth.dev</string>
|
||||||
|
<string>applinks:sn.solsynth.dev</string>
|
||||||
|
</array>
|
||||||
<key>com.apple.developer.usernotifications.communication</key>
|
<key>com.apple.developer.usernotifications.communication</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>group.solsynth.solian</string>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
14
ios/Runner/Service/Attachment.swift
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
//
|
||||||
|
// Attachment.swift
|
||||||
|
// Runner
|
||||||
|
//
|
||||||
|
// Created by LittleSheep on 2024/12/14.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
func getAttachmentUrl(for identifier: String) -> String {
|
||||||
|
let serverBaseUrl = "https://api.sn.solsynth.dev"
|
||||||
|
|
||||||
|
return identifier.starts(with: "http") ? identifier : "\(serverBaseUrl)/cgi/uc/attachments/\(identifier)"
|
||||||
|
}
|
@ -7,6 +7,8 @@
|
|||||||
|
|
||||||
import UserNotifications
|
import UserNotifications
|
||||||
import Intents
|
import Intents
|
||||||
|
import Kingfisher
|
||||||
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
enum ParseNotificationPayloadError: Error {
|
enum ParseNotificationPayloadError: Error {
|
||||||
case missingMetadata(String)
|
case missingMetadata(String)
|
||||||
@ -17,63 +19,6 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
|
|
||||||
private var contentHandler: ((UNNotificationContent) -> Void)?
|
private var contentHandler: ((UNNotificationContent) -> Void)?
|
||||||
private var bestAttemptContent: UNMutableNotificationContent?
|
private var bestAttemptContent: UNMutableNotificationContent?
|
||||||
private let serverBaseUrl = "https://api.sn.solsynth.dev"
|
|
||||||
|
|
||||||
private func getAttachmentUrl(for identifier: String) -> String {
|
|
||||||
identifier.starts(with: "http") ? identifier : "\(serverBaseUrl)/cgi/uc/attachments/\(identifier)"
|
|
||||||
}
|
|
||||||
|
|
||||||
private func fetchAvatarImage(from url: String, completion: @escaping (INImage?) -> Void) {
|
|
||||||
guard let imageURL = URL(string: url) else {
|
|
||||||
completion(nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define a cache location based on the URL hash
|
|
||||||
let cacheFileName = imageURL.lastPathComponent
|
|
||||||
let tempDirectory = FileManager.default.temporaryDirectory
|
|
||||||
let cachedFileUrl = tempDirectory.appendingPathComponent(cacheFileName)
|
|
||||||
|
|
||||||
// Check if the image is already cached
|
|
||||||
if FileManager.default.fileExists(atPath: cachedFileUrl.path) {
|
|
||||||
do {
|
|
||||||
let data = try Data(contentsOf: cachedFileUrl)
|
|
||||||
let cachedImage = INImage(imageData: data) // No optional binding here
|
|
||||||
completion(cachedImage)
|
|
||||||
return
|
|
||||||
} catch {
|
|
||||||
print("Failed to load cached avatar image: \(error.localizedDescription)")
|
|
||||||
try? FileManager.default.removeItem(at: cachedFileUrl) // Clear corrupted cache
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download the image if not cached
|
|
||||||
let session = URLSession(configuration: .default)
|
|
||||||
session.downloadTask(with: imageURL) { localUrl, response, error in
|
|
||||||
if let error = error {
|
|
||||||
print("Failed to fetch avatar image: \(error.localizedDescription)")
|
|
||||||
completion(nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let localUrl = localUrl, let data = try? Data(contentsOf: localUrl) else {
|
|
||||||
print("Failed to fetch data for avatar image.")
|
|
||||||
completion(nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
// Cache the downloaded file
|
|
||||||
try FileManager.default.moveItem(at: localUrl, to: cachedFileUrl)
|
|
||||||
} catch {
|
|
||||||
print("Failed to cache avatar image: \(error.localizedDescription)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create INImage from the downloaded data
|
|
||||||
let inImage = INImage(imageData: data) // Create directly
|
|
||||||
completion(inImage)
|
|
||||||
}.resume()
|
|
||||||
}
|
|
||||||
|
|
||||||
override func didReceive(
|
override func didReceive(
|
||||||
_ request: UNNotificationRequest,
|
_ request: UNNotificationRequest,
|
||||||
@ -117,16 +62,43 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
throw ParseNotificationPayloadError.missingAvatarUrl("The notification has no avatar.")
|
throw ParseNotificationPayloadError.missingAvatarUrl("The notification has no avatar.")
|
||||||
}
|
}
|
||||||
|
|
||||||
let avatarUrl = getAttachmentUrl(for: avatarIdentifier)
|
let replyableMessageCategory = UNNotificationCategory(
|
||||||
fetchAvatarImage(from: avatarUrl) { [weak self] inImage in
|
identifier: content.categoryIdentifier,
|
||||||
guard let self = self else { return }
|
actions: [
|
||||||
|
UNTextInputNotificationAction(
|
||||||
|
identifier: "reply_action",
|
||||||
|
title: "Reply",
|
||||||
|
options: []
|
||||||
|
),
|
||||||
|
],
|
||||||
|
intentIdentifiers: [],
|
||||||
|
options: []
|
||||||
|
)
|
||||||
|
|
||||||
let handle = INPersonHandle(value: "\(metadata["user_id"] ?? "")", type: .unknown)
|
UNUserNotificationCenter.current().setNotificationCategories([replyableMessageCategory])
|
||||||
|
content.categoryIdentifier = replyableMessageCategory.identifier
|
||||||
|
|
||||||
|
let metadataCopy = metadata as? [String: String] ?? [:]
|
||||||
|
let avatarUrl = getAttachmentUrl(for: avatarIdentifier)
|
||||||
|
|
||||||
|
let targetSize = 640
|
||||||
|
let scaleProcessor = ResizingImageProcessor(referenceSize: CGSize(width: targetSize, height: targetSize), mode: .aspectFit)
|
||||||
|
|
||||||
|
KingfisherManager.shared.retrieveImage(with: URL(string: avatarUrl)!, options: [.processor(scaleProcessor)], completionHandler: { result in
|
||||||
|
var image: Data?
|
||||||
|
switch result {
|
||||||
|
case .success(let value):
|
||||||
|
image = value.image.pngData()
|
||||||
|
case .failure(let error):
|
||||||
|
print("Unable to get avatar url: \(error)")
|
||||||
|
}
|
||||||
|
|
||||||
|
let handle = INPersonHandle(value: "\(metadataCopy["user_id"] ?? "")", type: .unknown)
|
||||||
let sender = INPerson(
|
let sender = INPerson(
|
||||||
personHandle: handle,
|
personHandle: handle,
|
||||||
nameComponents: nil,
|
nameComponents: nil,
|
||||||
displayName: content.title,
|
displayName: content.title,
|
||||||
image: inImage,
|
image: image == nil ? nil : INImage(imageData: image!),
|
||||||
contactIdentifier: nil,
|
contactIdentifier: nil,
|
||||||
customIdentifier: nil
|
customIdentifier: nil
|
||||||
)
|
)
|
||||||
@ -137,12 +109,12 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
let updatedContent = try? request.content.updating(from: intent)
|
let updatedContent = try? request.content.updating(from: intent)
|
||||||
self.contentHandler?(updatedContent ?? content)
|
self.contentHandler?(updatedContent ?? content)
|
||||||
} else {
|
} else {
|
||||||
let intent = self.createMessageIntent(with: sender, metadata: metadata, body: content.body)
|
let intent = self.createMessageIntent(with: sender, metadata: metadataCopy, body: content.body)
|
||||||
self.donateInteraction(for: intent)
|
self.donateInteraction(for: intent)
|
||||||
let updatedContent = try? request.content.updating(from: intent)
|
let updatedContent = try? request.content.updating(from: intent)
|
||||||
self.contentHandler?(updatedContent ?? content)
|
self.contentHandler?(updatedContent ?? content)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleDefaultNotification(content: UNMutableNotificationContent) throws {
|
private func handleDefaultNotification(content: UNMutableNotificationContent) throws {
|
||||||
@ -151,15 +123,15 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let imageIdentifier = metadata["image"] as? String {
|
if let imageIdentifier = metadata["image"] as? String {
|
||||||
attachMedia(to: content, withIdentifier: imageIdentifier)
|
attachMedia(to: content, withIdentifier: imageIdentifier, fileType: UTType.jpeg, doScaleDown: true)
|
||||||
} else if let avatarIdentifier = metadata["avatar"] as? String {
|
} else if let avatarIdentifier = metadata["avatar"] as? String {
|
||||||
attachMedia(to: content, withIdentifier: avatarIdentifier)
|
attachMedia(to: content, withIdentifier: avatarIdentifier, fileType: UTType.jpeg, doScaleDown: true)
|
||||||
}
|
} else {
|
||||||
|
|
||||||
contentHandler?(content)
|
contentHandler?(content)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func attachMedia(to content: UNMutableNotificationContent, withIdentifier identifier: String) {
|
private func attachMedia(to content: UNMutableNotificationContent, withIdentifier identifier: String, fileType type: UTType?, doScaleDown scaleDown: Bool = false) {
|
||||||
let attachmentUrl = getAttachmentUrl(for: identifier)
|
let attachmentUrl = getAttachmentUrl(for: identifier)
|
||||||
|
|
||||||
guard let remoteUrl = URL(string: attachmentUrl) else {
|
guard let remoteUrl = URL(string: attachmentUrl) else {
|
||||||
@ -167,49 +139,62 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define a cache location based on the identifier
|
let targetSize = 800
|
||||||
|
let scaleProcessor = ResizingImageProcessor(referenceSize: CGSize(width: targetSize, height: targetSize), mode: .aspectFit)
|
||||||
|
|
||||||
|
KingfisherManager.shared.retrieveImage(with: remoteUrl, options: scaleDown ? [
|
||||||
|
.processor(scaleProcessor)
|
||||||
|
] : nil) { [weak self] result in
|
||||||
|
guard let self = self else { return }
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
case .success(let retrievalResult):
|
||||||
|
// The image is either retrieved from cache or downloaded
|
||||||
let tempDirectory = FileManager.default.temporaryDirectory
|
let tempDirectory = FileManager.default.temporaryDirectory
|
||||||
let cachedFileUrl = tempDirectory.appendingPathComponent(identifier)
|
let cachedFileUrl = tempDirectory.appendingPathComponent(identifier)
|
||||||
|
|
||||||
if FileManager.default.fileExists(atPath: cachedFileUrl.path) {
|
|
||||||
// Use cached file
|
|
||||||
attachLocalMedia(to: content, from: cachedFileUrl, withIdentifier: identifier)
|
|
||||||
} else {
|
|
||||||
// Download and cache the file
|
|
||||||
let session = URLSession(configuration: .default)
|
|
||||||
session.downloadTask(with: remoteUrl) { [weak content] localUrl, response, error in
|
|
||||||
guard let content = content else { return }
|
|
||||||
|
|
||||||
if let error = error {
|
|
||||||
print("Failed to download media: \(error.localizedDescription)")
|
|
||||||
self.contentHandler?(content)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let localUrl = localUrl else {
|
|
||||||
print("No local file URL after download")
|
|
||||||
self.contentHandler?(content)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
do {
|
||||||
// Move the downloaded file to the cache
|
// Write the image data to a temporary file for UNNotificationAttachment
|
||||||
try FileManager.default.moveItem(at: localUrl, to: cachedFileUrl)
|
try retrievalResult.image.pngData()?.write(to: cachedFileUrl)
|
||||||
self.attachLocalMedia(to: content, from: cachedFileUrl, withIdentifier: identifier)
|
self.attachLocalMedia(to: content, fileType: type?.identifier, from: cachedFileUrl, withIdentifier: identifier)
|
||||||
} catch {
|
} catch {
|
||||||
print("Failed to cache media file: \(error.localizedDescription)")
|
print("Failed to write media to temporary file: \(error.localizedDescription)")
|
||||||
|
self.contentHandler?(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
case .failure(let error):
|
||||||
|
print("Failed to retrieve image: \(error.localizedDescription)")
|
||||||
self.contentHandler?(content)
|
self.contentHandler?(content)
|
||||||
}
|
}
|
||||||
}.resume()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func attachLocalMedia(to content: UNMutableNotificationContent, from localUrl: URL, withIdentifier identifier: String) {
|
private func attachLocalMedia(to content: UNMutableNotificationContent, fileType type: String?, from localUrl: URL, withIdentifier identifier: String) {
|
||||||
if let attachment = try? UNNotificationAttachment(identifier: identifier, url: localUrl) {
|
do {
|
||||||
|
let attachment = try UNNotificationAttachment(identifier: identifier, url: localUrl, options: [
|
||||||
|
UNNotificationAttachmentOptionsTypeHintKey: type as Any,
|
||||||
|
UNNotificationAttachmentOptionsThumbnailHiddenKey: 0,
|
||||||
|
])
|
||||||
content.attachments = [attachment]
|
content.attachments = [attachment]
|
||||||
} else {
|
} catch let error as NSError {
|
||||||
print("Failed to create attachment from cached file: \(localUrl.path)")
|
// Log detailed error information
|
||||||
|
print("Failed to create attachment from file at \(localUrl.path)")
|
||||||
|
print("Error: \(error.localizedDescription)")
|
||||||
|
|
||||||
|
// Check specific error codes if needed
|
||||||
|
if error.domain == NSCocoaErrorDomain {
|
||||||
|
switch error.code {
|
||||||
|
case NSFileReadNoSuchFileError:
|
||||||
|
print("File does not exist at \(localUrl.path)")
|
||||||
|
case NSFileReadNoPermissionError:
|
||||||
|
print("No permission to read file at \(localUrl.path)")
|
||||||
|
default:
|
||||||
|
print("Unhandled file error: \(error.code)")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call content handler regardless of success or failure
|
||||||
self.contentHandler?(content)
|
self.contentHandler?(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
ios/SolarNotifyService/SolarNotifyService.entitlements
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>group.solsynth.solian</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
24
ios/SolarShare/Base.lproj/MainInterface.storyboard
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="j1y-V4-xli">
|
||||||
|
<dependencies>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
|
||||||
|
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--Share View Controller-->
|
||||||
|
<scene sceneID="ceB-am-kn3">
|
||||||
|
<objects>
|
||||||
|
<viewController id="j1y-V4-xli" customClass="ShareViewController" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
|
<view key="view" opaque="NO" contentMode="scaleToFill" id="wbc-yd-nQP">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<viewLayoutGuide key="safeArea" id="1Xd-am-t49"/>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="CEy-Cv-SGf" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
</document>
|
36
ios/SolarShare/Info.plist
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PHSupportedMediaTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>Video</string>
|
||||||
|
<string>Image</string>
|
||||||
|
</array>
|
||||||
|
<key>NSExtension</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSExtensionAttributes</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSExtensionActivationRule</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSExtensionActivationSupportsText</key>
|
||||||
|
<true/>
|
||||||
|
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
|
||||||
|
<integer>15</integer>
|
||||||
|
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
|
||||||
|
<integer>15</integer>
|
||||||
|
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
|
||||||
|
<integer>15</integer>
|
||||||
|
<key>NSExtensionActivationSupportsFileWithMaxCount</key>
|
||||||
|
<integer>15</integer>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<key>NSExtensionMainStoryboard</key>
|
||||||
|
<string>MainInterface</string>
|
||||||
|
<key>NSExtensionPointIdentifier</key>
|
||||||
|
<string>com.apple.share-services</string>
|
||||||
|
</dict>
|
||||||
|
<key>AppGroupId</key>
|
||||||
|
<string>group.solsynth.solian</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
18
ios/SolarShare/ShareViewController.swift
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// ShareViewController.swift
|
||||||
|
// SolarShare
|
||||||
|
//
|
||||||
|
// Created by LittleSheep on 2024/12/15.
|
||||||
|
//
|
||||||
|
|
||||||
|
import receive_sharing_intent
|
||||||
|
|
||||||
|
class ShareViewController: RSIShareViewController {
|
||||||
|
|
||||||
|
// Use this method to return false if you don't want to redirect to host app automatically.
|
||||||
|
// Default is true
|
||||||
|
override func shouldAutoRedirect() -> Bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
10
ios/SolarShare/SolarShare.entitlements
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>group.solsynth.solian</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "dark"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"appearances" : [
|
||||||
|
{
|
||||||
|
"appearance" : "luminosity",
|
||||||
|
"value" : "tinted"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
6
ios/SolarWidget/Assets.xcassets/Contents.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
135
ios/SolarWidget/CheckInWidget.swift
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
//
|
||||||
|
// SolarWidget.swift
|
||||||
|
// SolarWidget
|
||||||
|
//
|
||||||
|
// Created by LittleSheep on 2024/12/14.
|
||||||
|
//
|
||||||
|
|
||||||
|
import WidgetKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct CheckInProvider: TimelineProvider {
|
||||||
|
func placeholder(in context: Context) -> CheckInEntry {
|
||||||
|
CheckInEntry(date: Date(), checkIn: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSnapshot(in context: Context, completion: @escaping (CheckInEntry) -> ()) {
|
||||||
|
let prefs = UserDefaults(suiteName: "group.solsynth.solian")
|
||||||
|
|
||||||
|
let dateFormatter = DateFormatter()
|
||||||
|
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'"
|
||||||
|
|
||||||
|
let jsonDecoder = JSONDecoder()
|
||||||
|
jsonDecoder.dateDecodingStrategy = .formatted(dateFormatter)
|
||||||
|
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||||
|
|
||||||
|
let checkInRaw = prefs?.string(forKey: "pas_check_in_record")
|
||||||
|
var checkIn: SolarCheckInRecord?
|
||||||
|
if let checkInRaw = checkInRaw {
|
||||||
|
checkIn = try! jsonDecoder.decode(SolarCheckInRecord.self, from: checkInRaw.data(using: .utf8)!)
|
||||||
|
if checkIn != nil && !Calendar.current.isDate(checkIn!.createdAt, inSameDayAs: Date()) {
|
||||||
|
checkIn = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let entry = CheckInEntry(
|
||||||
|
date: Date(),
|
||||||
|
checkIn: checkIn
|
||||||
|
)
|
||||||
|
completion(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
|
||||||
|
getSnapshot(in: context) { (entry) in
|
||||||
|
let timeline = Timeline(entries: [entry], policy: .atEnd)
|
||||||
|
completion(timeline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CheckInEntry: TimelineEntry {
|
||||||
|
let date: Date
|
||||||
|
let checkIn: SolarCheckInRecord?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CheckInWidgetEntryView : View {
|
||||||
|
var entry: CheckInProvider.Entry
|
||||||
|
|
||||||
|
private let resultTierSymbols: [String] = ["大凶", "凶", "中平", "吉", "大吉"]
|
||||||
|
|
||||||
|
func checkIn() -> Void {}
|
||||||
|
|
||||||
|
func seeDetail() -> Void {}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
if let checkIn = entry.checkIn {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text(resultTierSymbols[checkIn.resultTier]).font(.system(size: 27, weight: .bold))
|
||||||
|
Text("+\(checkIn.resultExperience) EXP").font(.system(size: 15, design: .monospaced))
|
||||||
|
}.padding(.horizontal, 4)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text(
|
||||||
|
checkIn.createdAt,
|
||||||
|
format: .dateTime.weekday()
|
||||||
|
).font(.system(size: 13))
|
||||||
|
Text(
|
||||||
|
checkIn.createdAt,
|
||||||
|
format: .dateTime.day().month()
|
||||||
|
).font(.system(size: 13))
|
||||||
|
}.padding(.leading, 4)
|
||||||
|
|
||||||
|
Button("See Detail", systemImage: "arrow.right", action: seeDetail)
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
|
.buttonBorderShape(.circle)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||||
|
}.frame(alignment: .bottom)
|
||||||
|
} else {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text("Check In").font(.system(size: 19, weight: .bold))
|
||||||
|
Text("You haven't check in today").font(.system(size: 15))
|
||||||
|
}.padding(.horizontal, 4)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
HStack(alignment: .bottom) {
|
||||||
|
Button("Check In", systemImage: "checkmark", action: checkIn).labelStyle(.iconOnly).buttonBorderShape(.circle).frame(maxWidth: .infinity, alignment: .trailing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.padding(8).widgetURL(URL(string: "https://sn.solsynth.dev"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CheckInWidget: Widget {
|
||||||
|
let kind: String = "SolarCheckInWidget"
|
||||||
|
|
||||||
|
var body: some WidgetConfiguration {
|
||||||
|
StaticConfiguration(kind: kind, provider: CheckInProvider()) { entry in
|
||||||
|
if #available(iOS 17.0, *) {
|
||||||
|
CheckInWidgetEntryView(entry: entry)
|
||||||
|
.containerBackground(.fill.tertiary, for: .widget)
|
||||||
|
} else {
|
||||||
|
CheckInWidgetEntryView(entry: entry)
|
||||||
|
.padding()
|
||||||
|
.background()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.configurationDisplayName("Check In")
|
||||||
|
.description("View your today's fortune on your home screen")
|
||||||
|
.supportedFamilies([.systemSmall, .systemMedium])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview(as: .systemSmall) {
|
||||||
|
CheckInWidget()
|
||||||
|
} timeline: {
|
||||||
|
CheckInEntry(date: .now, checkIn: nil)
|
||||||
|
CheckInEntry(
|
||||||
|
date: .now,
|
||||||
|
checkIn: SolarCheckInRecord(id: 1, resultTier: 1, resultExperience: 100, createdAt: Date.now)
|
||||||
|
)
|
||||||
|
}
|
11
ios/SolarWidget/Info.plist
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>NSExtension</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSExtensionPointIdentifier</key>
|
||||||
|
<string>com.apple.widgetkit-extension</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
246
ios/SolarWidget/RandomPostWidget.swift
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
//
|
||||||
|
// RandomPostWidget.swift
|
||||||
|
// Runner
|
||||||
|
//
|
||||||
|
// Created by LittleSheep on 2024/12/14.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import WidgetKit
|
||||||
|
import Kingfisher
|
||||||
|
|
||||||
|
struct RandomPostProvider: TimelineProvider {
|
||||||
|
func placeholder(in context: Context) -> RandomPostEntry {
|
||||||
|
RandomPostEntry(date: Date(), user: nil, randomPost: nil, family: .systemMedium)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSnapshot(in context: Context, completion: @escaping (RandomPostEntry) -> ()) {
|
||||||
|
let prefs = UserDefaults(suiteName: "group.solsynth.solian")
|
||||||
|
|
||||||
|
let dateFormatter = DateFormatter()
|
||||||
|
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'"
|
||||||
|
|
||||||
|
let jsonDecoder = JSONDecoder()
|
||||||
|
jsonDecoder.dateDecodingStrategy = .formatted(dateFormatter)
|
||||||
|
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||||
|
|
||||||
|
let userRaw = prefs?.string(forKey: "user")
|
||||||
|
var user: SolarUser?
|
||||||
|
if let userRaw = userRaw {
|
||||||
|
user = try! jsonDecoder.decode(SolarUser.self, from: userRaw.data(using: .utf8)!)
|
||||||
|
}
|
||||||
|
|
||||||
|
let randomPostRaw = prefs?.string(forKey: "int_random_post")
|
||||||
|
var randomPost: SolarPost?
|
||||||
|
if let randomPostRaw = randomPostRaw {
|
||||||
|
randomPost = try! jsonDecoder.decode(SolarPost.self, from: randomPostRaw.data(using: .utf8)!)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let entry = RandomPostEntry(
|
||||||
|
date: Date(),
|
||||||
|
user: user,
|
||||||
|
randomPost: randomPost,
|
||||||
|
family: context.family
|
||||||
|
)
|
||||||
|
completion(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
|
||||||
|
getSnapshot(in: context) { (entry) in
|
||||||
|
let timeline = Timeline(entries: [entry], policy: .atEnd)
|
||||||
|
completion(timeline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RandomPostEntry: TimelineEntry {
|
||||||
|
let date: Date
|
||||||
|
let user: SolarUser?
|
||||||
|
let randomPost: SolarPost?
|
||||||
|
|
||||||
|
let family: WidgetFamily
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RandomPostWidgetEntryView : View {
|
||||||
|
var entry: RandomPostProvider.Entry
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
if let randomPost = entry.randomPost {
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
HStack(alignment: .center) {
|
||||||
|
if let avatar = randomPost.publisher.avatar {
|
||||||
|
let avatarUrl = getAttachmentUrl(for: avatar)
|
||||||
|
let size: CGFloat = 28
|
||||||
|
let scaleProcessor = ResizingImageProcessor(referenceSize: CGSize(width: size, height: size), mode: .aspectFill)
|
||||||
|
|
||||||
|
KFImage.url(URL(string: avatarUrl))
|
||||||
|
.resizable()
|
||||||
|
.setProcessor(scaleProcessor)
|
||||||
|
.fade(duration: 0.25)
|
||||||
|
.placeholder{
|
||||||
|
ProgressView()
|
||||||
|
.progressViewStyle(CircularProgressViewStyle())
|
||||||
|
}
|
||||||
|
.aspectRatio(contentMode: .fill)
|
||||||
|
.frame(width: size, height: size)
|
||||||
|
.cornerRadius(size / 2)
|
||||||
|
|
||||||
|
.frame(width: size, height: size, alignment: .center)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(randomPost.publisher.nick)
|
||||||
|
.font(.system(size: 15))
|
||||||
|
.opacity(0.9)
|
||||||
|
|
||||||
|
Text("@\(randomPost.publisher.name)")
|
||||||
|
.font(.system(size: 13, design: .monospaced))
|
||||||
|
.opacity(0.9)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}.frame(maxWidth: .infinity).padding(.bottom, 12)
|
||||||
|
|
||||||
|
if randomPost.body.title != nil || randomPost.body.description != nil {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
if let title = randomPost.body.title {
|
||||||
|
Text(title)
|
||||||
|
.font(.system(size: 17))
|
||||||
|
}
|
||||||
|
if let description = randomPost.body.description {
|
||||||
|
Text(description)
|
||||||
|
.font(.system(size: 15))
|
||||||
|
}
|
||||||
|
}.padding(.bottom, 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let content = randomPost.body.content {
|
||||||
|
if (randomPost.body.title == nil && randomPost.body.description == nil) || entry.family == .systemLarge || entry.family == .systemExtraLarge {
|
||||||
|
Text(
|
||||||
|
(entry.family == .systemLarge || entry.family == .systemExtraLarge) ? content : content.replacingOccurrences(of: "\n", with: " ")
|
||||||
|
)
|
||||||
|
.font(.system(size: 15))
|
||||||
|
} else {
|
||||||
|
Text("\(Image(systemName: "plus")) total \(content.count) characters")
|
||||||
|
.font(.system(size: 11, design: .monospaced))
|
||||||
|
.opacity(0.75)
|
||||||
|
.padding(.top, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let attachment = randomPost.body.attachments {
|
||||||
|
if attachment.count == 1 {
|
||||||
|
Text("\(Image(systemName: "document.fill")) \(attachment.count) attachment")
|
||||||
|
.font(.system(size: 11, design: .monospaced))
|
||||||
|
.opacity(0.75)
|
||||||
|
.padding(.top, 2)
|
||||||
|
} else if attachment.count > 1 {
|
||||||
|
Text("\(Image(systemName: "document.fill")) \(attachment.count) attachments")
|
||||||
|
.font(.system(size: 11, design: .monospaced))
|
||||||
|
.opacity(0.75)
|
||||||
|
.padding(.top, 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Text(randomPost.publishedAt!, format: .dateTime)
|
||||||
|
.font(.system(size: 11))
|
||||||
|
Text("#\(randomPost.id)")
|
||||||
|
.font(.system(size: 9))
|
||||||
|
}.widgetURL(URL(string: "https://sn.solsynth.dev/posts/\(randomPost.id)"))
|
||||||
|
} else {
|
||||||
|
VStack(alignment: .center) {
|
||||||
|
Text("No Recommendations").font(.system(size: 19, weight: .bold))
|
||||||
|
Text("Open the app to load some random post")
|
||||||
|
.font(.system(size: 15))
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
}.frame(alignment: .center)
|
||||||
|
}
|
||||||
|
}.padding(8).frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RandomPostWidget: Widget {
|
||||||
|
let kind: String = "SolarRandomPostWidget"
|
||||||
|
|
||||||
|
var body: some WidgetConfiguration {
|
||||||
|
StaticConfiguration(kind: kind, provider: RandomPostProvider()) { entry in
|
||||||
|
if #available(iOS 17.0, *) {
|
||||||
|
RandomPostWidgetEntryView(entry: entry)
|
||||||
|
.containerBackground(.fill.tertiary, for: .widget)
|
||||||
|
} else {
|
||||||
|
RandomPostWidgetEntryView(entry: entry)
|
||||||
|
.padding()
|
||||||
|
.background()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.configurationDisplayName("Random Post")
|
||||||
|
.description("View the random post on the Solar Network")
|
||||||
|
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge, .systemExtraLarge])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview(as: .systemSmall) {
|
||||||
|
RandomPostWidget()
|
||||||
|
} timeline: {
|
||||||
|
RandomPostEntry(date: Date.now, user: nil, randomPost: nil, family: .systemLarge)
|
||||||
|
RandomPostEntry(
|
||||||
|
date: .now,
|
||||||
|
user: SolarUser(id: 1, name: "demo", nick: "Deemo"),
|
||||||
|
randomPost: SolarPost(
|
||||||
|
id: 1,
|
||||||
|
body: SolarPostBody(
|
||||||
|
content: "Hello, World",
|
||||||
|
title: nil,
|
||||||
|
description: nil,
|
||||||
|
attachments: ["zb2hiUEmYcnpHfVN"]
|
||||||
|
),
|
||||||
|
publisher: SolarPublisher(
|
||||||
|
id: 1,
|
||||||
|
name: "demo",
|
||||||
|
nick: "Deemo",
|
||||||
|
description: nil,
|
||||||
|
avatar: "IZxCFkJUPKRijFCx",
|
||||||
|
banner: nil,
|
||||||
|
createdAt: .now,
|
||||||
|
updatedAt: .now
|
||||||
|
),
|
||||||
|
publisherId: 1,
|
||||||
|
createdAt: .now,
|
||||||
|
updatedAt: .now,
|
||||||
|
editedAt: nil,
|
||||||
|
publishedAt: .now
|
||||||
|
),
|
||||||
|
family: .systemSmall
|
||||||
|
)
|
||||||
|
RandomPostEntry(
|
||||||
|
date: .now,
|
||||||
|
user: SolarUser(id: 1, name: "demo", nick: "Deemo"),
|
||||||
|
randomPost: SolarPost(
|
||||||
|
id: 1,
|
||||||
|
body: SolarPostBody(
|
||||||
|
content: "Hello, World\nOh wow",
|
||||||
|
title: "Title",
|
||||||
|
description: "Description",
|
||||||
|
attachments: ["zb2hiUEmYcnpHfVN"]
|
||||||
|
),
|
||||||
|
publisher: SolarPublisher(
|
||||||
|
id: 1,
|
||||||
|
name: "demo",
|
||||||
|
nick: "Deemo",
|
||||||
|
description: nil,
|
||||||
|
avatar: "IZxCFkJUPKRijFCx",
|
||||||
|
banner: nil,
|
||||||
|
createdAt: .now,
|
||||||
|
updatedAt: .now
|
||||||
|
),
|
||||||
|
publisherId: 1,
|
||||||
|
createdAt: .now,
|
||||||
|
updatedAt: .now,
|
||||||
|
editedAt: nil,
|
||||||
|
publishedAt: .now
|
||||||
|
),
|
||||||
|
family: .systemLarge
|
||||||
|
)
|
||||||
|
}
|
17
ios/SolarWidget/SolarWidgetBundle.swift
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
//
|
||||||
|
// SolarWidgetBundle.swift
|
||||||
|
// SolarWidget
|
||||||
|
//
|
||||||
|
// Created by LittleSheep on 2024/12/14.
|
||||||
|
//
|
||||||
|
|
||||||
|
import WidgetKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct SolarWidgetBundle: WidgetBundle {
|
||||||
|
var body: some Widget {
|
||||||
|
CheckInWidget()
|
||||||
|
RandomPostWidget()
|
||||||
|
}
|
||||||
|
}
|
10
ios/SolarWidgetExtension.entitlements
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>group.solsynth.solian</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -1,4 +1,5 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
@ -152,6 +153,7 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
final TextEditingController contentController = TextEditingController();
|
final TextEditingController contentController = TextEditingController();
|
||||||
final TextEditingController titleController = TextEditingController();
|
final TextEditingController titleController = TextEditingController();
|
||||||
final TextEditingController descriptionController = TextEditingController();
|
final TextEditingController descriptionController = TextEditingController();
|
||||||
|
final TextEditingController aliasController = TextEditingController();
|
||||||
|
|
||||||
PostWriteController() {
|
PostWriteController() {
|
||||||
titleController.addListener(() => notifyListeners());
|
titleController.addListener(() => notifyListeners());
|
||||||
@ -176,6 +178,7 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
List<int> visibleUsers = List.empty();
|
List<int> visibleUsers = List.empty();
|
||||||
List<int> invisibleUsers = List.empty();
|
List<int> invisibleUsers = List.empty();
|
||||||
List<String> tags = List.empty();
|
List<String> tags = List.empty();
|
||||||
|
List<String> categories = List.empty();
|
||||||
PostWriteMedia? thumbnail;
|
PostWriteMedia? thumbnail;
|
||||||
List<PostWriteMedia> attachments = List.empty(growable: true);
|
List<PostWriteMedia> attachments = List.empty(growable: true);
|
||||||
DateTime? publishedAt, publishedUntil;
|
DateTime? publishedAt, publishedUntil;
|
||||||
@ -198,12 +201,14 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
titleController.text = post.body['title'] ?? '';
|
titleController.text = post.body['title'] ?? '';
|
||||||
descriptionController.text = post.body['description'] ?? '';
|
descriptionController.text = post.body['description'] ?? '';
|
||||||
contentController.text = post.body['content'] ?? '';
|
contentController.text = post.body['content'] ?? '';
|
||||||
|
aliasController.text = post.alias ?? '';
|
||||||
publishedAt = post.publishedAt;
|
publishedAt = post.publishedAt;
|
||||||
publishedUntil = post.publishedUntil;
|
publishedUntil = post.publishedUntil;
|
||||||
visibleUsers = List.from(post.visibleUsersList ?? []);
|
visibleUsers = List.from(post.visibleUsersList ?? []);
|
||||||
invisibleUsers = List.from(post.invisibleUsersList ?? []);
|
invisibleUsers = List.from(post.invisibleUsersList ?? []);
|
||||||
visibility = post.visibility;
|
visibility = post.visibility;
|
||||||
tags = List.from(post.tags.map((ele) => ele.alias));
|
tags = List.from(post.tags.map((ele) => ele.alias));
|
||||||
|
categories = List.from(post.categories.map((ele) => ele.alias));
|
||||||
attachments.addAll(post.preload?.attachments?.map((ele) => PostWriteMedia(ele)) ?? []);
|
attachments.addAll(post.preload?.attachments?.map((ele) => PostWriteMedia(ele)) ?? []);
|
||||||
|
|
||||||
if (post.preload?.thumbnail != null && (post.preload?.thumbnail?.rid.isNotEmpty ?? false)) {
|
if (post.preload?.thumbnail != null && (post.preload?.thumbnail?.rid.isNotEmpty ?? false)) {
|
||||||
@ -269,7 +274,7 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> post(BuildContext context) async {
|
Future<void> sendPost(BuildContext context) async {
|
||||||
if (isBusy || publisher == null) return;
|
if (isBusy || publisher == null) return;
|
||||||
|
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
@ -305,12 +310,14 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
place.$2,
|
place.$2,
|
||||||
onProgress: (progress) {
|
onProgress: (progress) {
|
||||||
// Calculate overall progress for attachments
|
// Calculate overall progress for attachments
|
||||||
progress = ((i + progress) / attachments.length) * kAttachmentProgressWeight;
|
progress = math.max(((i + progress) / attachments.length) * kAttachmentProgressWeight, progress);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
progress = (i + 1) / attachments.length * kAttachmentProgressWeight;
|
||||||
attachments[i] = PostWriteMedia(item);
|
attachments[i] = PostWriteMedia(item);
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
isBusy = false;
|
isBusy = false;
|
||||||
@ -334,11 +341,13 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
data: {
|
data: {
|
||||||
'publisher': publisher!.id,
|
'publisher': publisher!.id,
|
||||||
'content': contentController.text,
|
'content': contentController.text,
|
||||||
|
if (aliasController.text.isNotEmpty) 'alias': aliasController.text,
|
||||||
if (titleController.text.isNotEmpty) 'title': titleController.text,
|
if (titleController.text.isNotEmpty) 'title': titleController.text,
|
||||||
if (descriptionController.text.isNotEmpty) 'description': descriptionController.text,
|
if (descriptionController.text.isNotEmpty) 'description': descriptionController.text,
|
||||||
if (thumbnail != null && thumbnail!.attachment != null) 'thumbnail': thumbnail!.attachment!.rid,
|
if (thumbnail != null && thumbnail!.attachment != null) 'thumbnail': thumbnail!.attachment!.rid,
|
||||||
'attachments': attachments.where((e) => e.attachment != null).map((e) => e.attachment!.rid).toList(),
|
'attachments': attachments.where((e) => e.attachment != null).map((e) => e.attachment!.rid).toList(),
|
||||||
'tags': tags.map((ele) => {'alias': ele}).toList(),
|
'tags': tags.map((ele) => {'alias': ele}).toList(),
|
||||||
|
'categories': categories.map((ele) => {'alias': ele}).toList(),
|
||||||
'visibility': visibility,
|
'visibility': visibility,
|
||||||
'visible_users_list': visibleUsers,
|
'visible_users_list': visibleUsers,
|
||||||
'invisible_users_list': invisibleUsers,
|
'invisible_users_list': invisibleUsers,
|
||||||
@ -425,6 +434,11 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setCategories(List<String> value) {
|
||||||
|
categories = value;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
void setVisibility(int value) {
|
void setVisibility(int value) {
|
||||||
visibility = value;
|
visibility = value;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@ -461,6 +475,9 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
titleController.clear();
|
titleController.clear();
|
||||||
descriptionController.clear();
|
descriptionController.clear();
|
||||||
contentController.clear();
|
contentController.clear();
|
||||||
|
aliasController.clear();
|
||||||
|
tags.clear();
|
||||||
|
categories.clear();
|
||||||
attachments.clear();
|
attachments.clear();
|
||||||
editingPost = null;
|
editingPost = null;
|
||||||
replyingPost = null;
|
replyingPost = null;
|
||||||
@ -474,6 +491,7 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
contentController.dispose();
|
contentController.dispose();
|
||||||
titleController.dispose();
|
titleController.dispose();
|
||||||
descriptionController.dispose();
|
descriptionController.dispose();
|
||||||
|
aliasController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
133
lib/main.dart
@ -1,7 +1,10 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:developer';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||||
import 'package:croppy/croppy.dart';
|
import 'package:croppy/croppy.dart';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:easy_localization_loader/easy_localization_loader.dart';
|
import 'package:easy_localization_loader/easy_localization_loader.dart';
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
@ -10,29 +13,55 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:relative_time/relative_time.dart';
|
import 'package:relative_time/relative_time.dart';
|
||||||
import 'package:responsive_framework/responsive_framework.dart';
|
import 'package:responsive_framework/responsive_framework.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/firebase_options.dart';
|
import 'package:surface/firebase_options.dart';
|
||||||
import 'package:surface/providers/channel.dart';
|
import 'package:surface/providers/channel.dart';
|
||||||
import 'package:surface/providers/chat_call.dart';
|
import 'package:surface/providers/chat_call.dart';
|
||||||
|
import 'package:surface/providers/config.dart';
|
||||||
|
import 'package:surface/providers/link_preview.dart';
|
||||||
import 'package:surface/providers/navigation.dart';
|
import 'package:surface/providers/navigation.dart';
|
||||||
import 'package:surface/providers/notification.dart';
|
import 'package:surface/providers/notification.dart';
|
||||||
import 'package:surface/providers/post.dart';
|
import 'package:surface/providers/post.dart';
|
||||||
import 'package:surface/providers/relationship.dart';
|
import 'package:surface/providers/relationship.dart';
|
||||||
import 'package:surface/providers/sn_attachment.dart';
|
import 'package:surface/providers/sn_attachment.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/providers/special_day.dart';
|
||||||
import 'package:surface/providers/theme.dart';
|
import 'package:surface/providers/theme.dart';
|
||||||
import 'package:surface/providers/user_directory.dart';
|
import 'package:surface/providers/user_directory.dart';
|
||||||
import 'package:surface/providers/userinfo.dart';
|
import 'package:surface/providers/userinfo.dart';
|
||||||
import 'package:surface/providers/websocket.dart';
|
import 'package:surface/providers/websocket.dart';
|
||||||
|
import 'package:surface/providers/widget.dart';
|
||||||
import 'package:surface/router.dart';
|
import 'package:surface/router.dart';
|
||||||
import 'package:surface/types/chat.dart';
|
import 'package:surface/types/chat.dart';
|
||||||
import 'package:surface/types/realm.dart';
|
import 'package:surface/types/realm.dart';
|
||||||
import 'package:flutter_web_plugins/url_strategy.dart' show usePathUrlStrategy;
|
import 'package:flutter_web_plugins/url_strategy.dart' show usePathUrlStrategy;
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/version_label.dart';
|
import 'package:surface/widgets/version_label.dart';
|
||||||
|
import 'package:version/version.dart';
|
||||||
|
import 'package:workmanager/workmanager.dart';
|
||||||
|
import 'package:in_app_review/in_app_review.dart';
|
||||||
|
|
||||||
|
@pragma('vm:entry-point')
|
||||||
|
void appBackgroundDispatcher() {
|
||||||
|
Workmanager().executeTask((task, inputData) async {
|
||||||
|
log("[WorkManager] Native called background task: $task");
|
||||||
|
switch (task) {
|
||||||
|
case Workmanager.iOSBackgroundTask:
|
||||||
|
await Future.wait([widgetUpdateRandomPost()]);
|
||||||
|
return true;
|
||||||
|
case "WidgetUpdateRandomPost":
|
||||||
|
await widgetUpdateRandomPost();
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
@ -60,6 +89,22 @@ void main() async {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
||||||
|
Workmanager().initialize(
|
||||||
|
appBackgroundDispatcher,
|
||||||
|
isInDebugMode: kDebugMode,
|
||||||
|
);
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
Workmanager().registerPeriodicTask(
|
||||||
|
"widget-update-random-post",
|
||||||
|
"WidgetUpdateRandomPost",
|
||||||
|
frequency: Duration(minutes: 1),
|
||||||
|
constraints: Constraints(networkType: NetworkType.connected),
|
||||||
|
tag: "widget-update",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
runApp(const SolianApp());
|
runApp(const SolianApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,21 +127,31 @@ class SolianApp extends StatelessWidget {
|
|||||||
assetLoader: JsonAssetLoader(),
|
assetLoader: JsonAssetLoader(),
|
||||||
child: MultiProvider(
|
child: MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
|
// System extensions layer
|
||||||
|
Provider(create: (ctx) => HomeWidgetProvider(ctx)),
|
||||||
|
|
||||||
|
// Preferences layer
|
||||||
|
ChangeNotifierProvider(create: (ctx) => ConfigProvider(ctx)),
|
||||||
|
|
||||||
// Display layer
|
// Display layer
|
||||||
ChangeNotifierProvider(create: (_) => ThemeProvider()),
|
ChangeNotifierProvider(create: (_) => ThemeProvider()),
|
||||||
ChangeNotifierProvider(create: (ctx) => NavigationProvider()),
|
ChangeNotifierProvider(create: (ctx) => NavigationProvider()),
|
||||||
|
|
||||||
// Data layer
|
// Data layer
|
||||||
Provider(create: (_) => SnNetworkProvider()),
|
Provider(create: (ctx) => SnNetworkProvider(ctx)),
|
||||||
Provider(create: (ctx) => UserDirectoryProvider(ctx)),
|
Provider(create: (ctx) => UserDirectoryProvider(ctx)),
|
||||||
Provider(create: (ctx) => SnAttachmentProvider(ctx)),
|
Provider(create: (ctx) => SnAttachmentProvider(ctx)),
|
||||||
Provider(create: (ctx) => SnPostContentProvider(ctx)),
|
Provider(create: (ctx) => SnPostContentProvider(ctx)),
|
||||||
Provider(create: (ctx) => SnRelationshipProvider(ctx)),
|
Provider(create: (ctx) => SnRelationshipProvider(ctx)),
|
||||||
|
Provider(create: (ctx) => SnLinkPreviewProvider(ctx)),
|
||||||
ChangeNotifierProvider(create: (ctx) => UserProvider(ctx)),
|
ChangeNotifierProvider(create: (ctx) => UserProvider(ctx)),
|
||||||
ChangeNotifierProvider(create: (ctx) => WebSocketProvider(ctx)),
|
ChangeNotifierProvider(create: (ctx) => WebSocketProvider(ctx)),
|
||||||
ChangeNotifierProvider(create: (ctx) => NotificationProvider(ctx)),
|
ChangeNotifierProvider(create: (ctx) => NotificationProvider(ctx)),
|
||||||
ChangeNotifierProvider(create: (ctx) => ChatChannelProvider(ctx)),
|
ChangeNotifierProvider(create: (ctx) => ChatChannelProvider(ctx)),
|
||||||
ChangeNotifierProvider(create: (ctx) => ChatCallProvider(ctx)),
|
ChangeNotifierProvider(create: (ctx) => ChatCallProvider(ctx)),
|
||||||
|
|
||||||
|
// Additional helper layer
|
||||||
|
Provider(create: (ctx) => SpecialDayProvider(ctx)),
|
||||||
],
|
],
|
||||||
child: _AppDelegate(),
|
child: _AppDelegate(),
|
||||||
),
|
),
|
||||||
@ -111,7 +166,7 @@ class SolianApp extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _AppDelegate extends StatelessWidget {
|
class _AppDelegate extends StatelessWidget {
|
||||||
const _AppDelegate({super.key});
|
const _AppDelegate();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -134,7 +189,10 @@ class _AppDelegate extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
routerConfig: appRouter,
|
routerConfig: appRouter,
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
return _AppSplashScreen(child: child!);
|
return _AppSplashScreen(
|
||||||
|
key: const Key('global-splash-screen'),
|
||||||
|
child: child!,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -152,10 +210,66 @@ class _AppSplashScreen extends StatefulWidget {
|
|||||||
class _AppSplashScreenState extends State<_AppSplashScreen> {
|
class _AppSplashScreenState extends State<_AppSplashScreen> {
|
||||||
bool _isReady = false;
|
bool _isReady = false;
|
||||||
|
|
||||||
|
void _tryRequestRating() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
if (prefs.containsKey('first_boot_time')) {
|
||||||
|
final rawTime = prefs.getString('first_boot_time');
|
||||||
|
final time = DateTime.tryParse(rawTime ?? '');
|
||||||
|
if (time != null && time.isBefore(DateTime.now().subtract(const Duration(days: 3)))) {
|
||||||
|
final inAppReview = InAppReview.instance;
|
||||||
|
if (prefs.getBool('rating_requested') == true) return;
|
||||||
|
if (await inAppReview.isAvailable()) {
|
||||||
|
await inAppReview.requestReview();
|
||||||
|
prefs.setBool('rating_requested', true);
|
||||||
|
} else {
|
||||||
|
log('Unable request app review, unavailable');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prefs.setString('first_boot_time', DateTime.now().toIso8601String());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _checkForUpdate() async {
|
||||||
|
if (kIsWeb) return;
|
||||||
|
try {
|
||||||
|
final info = await PackageInfo.fromPlatform();
|
||||||
|
final localVersionString = '${info.version}+${info.buildNumber}';
|
||||||
|
final resp = await Dio(
|
||||||
|
BaseOptions(
|
||||||
|
sendTimeout: const Duration(seconds: 60),
|
||||||
|
receiveTimeout: const Duration(seconds: 60),
|
||||||
|
),
|
||||||
|
).get(
|
||||||
|
'https://git.solsynth.dev/api/v1/repos/HyperNet/Surface/tags?page=1&limit=1',
|
||||||
|
);
|
||||||
|
final remoteVersionString = (resp.data as List).firstOrNull?['name'] ?? '0.0.0+0';
|
||||||
|
final remoteVersion = Version.parse(remoteVersionString.split('+').first);
|
||||||
|
final localVersion = Version.parse(localVersionString.split('+').first);
|
||||||
|
final remoteBuildNumber = int.tryParse(remoteVersionString.split('+').last) ?? 0;
|
||||||
|
final localBuildNumber = int.tryParse(localVersionString.split('+').last) ?? 0;
|
||||||
|
log("[Update] Local: $localVersionString, Remote: $remoteVersionString");
|
||||||
|
if ((remoteVersion > localVersion || remoteBuildNumber > localBuildNumber) && mounted) {
|
||||||
|
final config = context.read<ConfigProvider>();
|
||||||
|
config.setUpdate(remoteVersionString);
|
||||||
|
log("[Update] Update available: $remoteVersionString");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) context.showErrorDialog('Unable to check update: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _initialize() async {
|
Future<void> _initialize() async {
|
||||||
try {
|
try {
|
||||||
|
final home = context.read<HomeWidgetProvider>();
|
||||||
|
await home.initialize();
|
||||||
|
if (!mounted) return;
|
||||||
|
// The Network initialization must be done after the HomeWidget initialization
|
||||||
|
// The Network initialization will save the server url to the HomeWidget
|
||||||
|
// The Network initialization will also save initialize the Config, so it not need to be initialized again
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
await sn.initializeUserAgent();
|
await sn.initializeUserAgent();
|
||||||
|
await sn.setConfigWithNative();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
final ua = context.read<UserProvider>();
|
final ua = context.read<UserProvider>();
|
||||||
await ua.initialize();
|
await ua.initialize();
|
||||||
@ -173,10 +287,18 @@ class _AppSplashScreenState extends State<_AppSplashScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _postInitialization() async {
|
||||||
|
await widgetUpdateRandomPost();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_initialize();
|
_initialize().then((_) {
|
||||||
|
_postInitialization();
|
||||||
|
_tryRequestRating();
|
||||||
|
_checkForUpdate();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -190,6 +312,9 @@ class _AppSplashScreenState extends State<_AppSplashScreen> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
if (MediaQuery.of(context).platformBrightness == Brightness.dark)
|
||||||
|
Image.asset("assets/icon/icon-dark.png", width: 64, height: 64)
|
||||||
|
else
|
||||||
Image.asset("assets/icon/icon.png", width: 64, height: 64),
|
Image.asset("assets/icon/icon.png", width: 64, height: 64),
|
||||||
const Gap(6),
|
const Gap(6),
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
|
@ -125,10 +125,8 @@ class ChatChannelProvider extends ChangeNotifier {
|
|||||||
final channelBox = await Hive.openBox<SnChatMessage>(
|
final channelBox = await Hive.openBox<SnChatMessage>(
|
||||||
'${ChatMessageController.kChatMessageBoxPrefix}${channel.id}',
|
'${ChatMessageController.kChatMessageBoxPrefix}${channel.id}',
|
||||||
);
|
);
|
||||||
final lastMessage = channelBox.isNotEmpty
|
final lastMessage =
|
||||||
? channelBox.values
|
channelBox.isNotEmpty ? channelBox.values.reduce((a, b) => a.createdAt.isAfter(b.createdAt) ? a : b) : null;
|
||||||
.reduce((a, b) => a.createdAt.isAfter(b.createdAt) ? a : b)
|
|
||||||
: null;
|
|
||||||
if (lastMessage != null) result.add(lastMessage);
|
if (lastMessage != null) result.add(lastMessage);
|
||||||
channelBox.close();
|
channelBox.close();
|
||||||
}
|
}
|
||||||
|
55
lib/providers/config.dart
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:surface/providers/widget.dart';
|
||||||
|
|
||||||
|
const kAtkStoreKey = 'nex_user_atk';
|
||||||
|
const kRtkStoreKey = 'nex_user_rtk';
|
||||||
|
|
||||||
|
const kNetworkServerDefault = 'https://api.sn.solsynth.dev';
|
||||||
|
const kNetworkServerStoreKey = 'app_server_url';
|
||||||
|
|
||||||
|
const kAppbarTransparentStoreKey = 'app_bar_transparent';
|
||||||
|
const kAppBackgroundStoreKey = 'app_has_background';
|
||||||
|
const kAppColorSchemeStoreKey = 'app_color_scheme';
|
||||||
|
|
||||||
|
const Map<String, FilterQuality> kImageQualityLevel = {
|
||||||
|
'settingsImageQualityLowest': FilterQuality.none,
|
||||||
|
'settingsImageQualityLow': FilterQuality.low,
|
||||||
|
'settingsImageQualityMedium': FilterQuality.medium,
|
||||||
|
'settingsImageQualityHigh': FilterQuality.high,
|
||||||
|
};
|
||||||
|
|
||||||
|
class ConfigProvider extends ChangeNotifier {
|
||||||
|
late final SharedPreferences prefs;
|
||||||
|
|
||||||
|
late final HomeWidgetProvider _home;
|
||||||
|
|
||||||
|
ConfigProvider(BuildContext context) {
|
||||||
|
_home = context.read<HomeWidgetProvider>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> initialize() async {
|
||||||
|
prefs = await SharedPreferences.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
FilterQuality get imageQuality {
|
||||||
|
return kImageQualityLevel.values.elementAtOrNull(prefs.getInt('app_image_quality') ?? 3) ?? FilterQuality.high;
|
||||||
|
}
|
||||||
|
|
||||||
|
String get serverUrl {
|
||||||
|
return prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
set serverUrl(String url) {
|
||||||
|
prefs.setString(kNetworkServerStoreKey, url);
|
||||||
|
_home.saveWidgetData("nex_server_url", url);
|
||||||
|
}
|
||||||
|
|
||||||
|
String? updatableVersion;
|
||||||
|
|
||||||
|
void setUpdate(String newVersion) {
|
||||||
|
updatableVersion = newVersion;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
41
lib/providers/experience.dart
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
const List<int> kExperienceToLevelRequirements = [
|
||||||
|
0, // Level 0
|
||||||
|
1000, // Level 1
|
||||||
|
4000, // Level 2
|
||||||
|
9000, // Level 3
|
||||||
|
16000, // Level 4
|
||||||
|
25000, // Level 5
|
||||||
|
36000, // Level 6
|
||||||
|
49000, // Level 7
|
||||||
|
64000, // Level 8
|
||||||
|
81000, // Level 9
|
||||||
|
100000, // Level 10
|
||||||
|
121000, // Level 11
|
||||||
|
144000, // Level 12
|
||||||
|
368000 // Level 13
|
||||||
|
];
|
||||||
|
|
||||||
|
int getLevelFromExp(int experience) {
|
||||||
|
final exp = kExperienceToLevelRequirements.reversed.firstWhere((x) => x <= experience);
|
||||||
|
final idx = kExperienceToLevelRequirements.indexOf(exp);
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
double calcLevelUpProgress(int experience) {
|
||||||
|
final exp = kExperienceToLevelRequirements.reversed.firstWhere((x) => x <= experience);
|
||||||
|
final idx = kExperienceToLevelRequirements.indexOf(exp);
|
||||||
|
if (idx + 1 >= kExperienceToLevelRequirements.length) return 1;
|
||||||
|
final nextExp = kExperienceToLevelRequirements[idx + 1];
|
||||||
|
return (experience - exp).abs() / (exp - nextExp).abs();
|
||||||
|
}
|
||||||
|
|
||||||
|
String calcLevelUpProgressLevel(int experience) {
|
||||||
|
final exp = kExperienceToLevelRequirements.reversed.firstWhere((x) => x <= experience);
|
||||||
|
final idx = kExperienceToLevelRequirements.indexOf(exp);
|
||||||
|
if (idx + 1 >= kExperienceToLevelRequirements.length) return 'Infinity';
|
||||||
|
final nextExp = exp - kExperienceToLevelRequirements[idx + 1];
|
||||||
|
final formatter = NumberFormat.compactCurrency(symbol: '', decimalDigits: 1);
|
||||||
|
return '${formatter.format((exp - experience).abs())}/${formatter.format(nextExp.abs())}';
|
||||||
|
}
|
35
lib/providers/link_preview.dart
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/types/link.dart';
|
||||||
|
|
||||||
|
class SnLinkPreviewProvider {
|
||||||
|
late final SnNetworkProvider _sn;
|
||||||
|
|
||||||
|
final Map<String, SnLinkMeta> _cache = {};
|
||||||
|
|
||||||
|
SnLinkPreviewProvider(BuildContext context) {
|
||||||
|
_sn = context.read<SnNetworkProvider>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<SnLinkMeta?> getLinkMeta(String url) async {
|
||||||
|
final b64 = utf8.fuse(base64Url);
|
||||||
|
final target = b64.encode(url);
|
||||||
|
if (_cache.containsKey(target)) return _cache[target];
|
||||||
|
|
||||||
|
log('[LinkPreview] Fetching $url ($target)');
|
||||||
|
|
||||||
|
try {
|
||||||
|
final resp = await _sn.client.get('/cgi/re/link/$target');
|
||||||
|
final meta = SnLinkMeta.fromJson(resp.data);
|
||||||
|
_cache[url] = meta;
|
||||||
|
return meta;
|
||||||
|
} catch (err) {
|
||||||
|
log('[LinkPreview] Failed to fetch $url ($target)...');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -83,12 +83,16 @@ class SnPostContentProvider {
|
|||||||
int offset = 0,
|
int offset = 0,
|
||||||
String? type,
|
String? type,
|
||||||
String? author,
|
String? author,
|
||||||
|
Iterable<String>? categories,
|
||||||
|
Iterable<String>? tags,
|
||||||
}) async {
|
}) async {
|
||||||
final resp = await _sn.client.get('/cgi/co/posts', queryParameters: {
|
final resp = await _sn.client.get('/cgi/co/posts', queryParameters: {
|
||||||
'take': take,
|
'take': take,
|
||||||
'offset': offset,
|
'offset': offset,
|
||||||
if (type != null) 'type': type,
|
if (type != null) 'type': type,
|
||||||
if (author != null) 'author': author,
|
if (author != null) 'author': author,
|
||||||
|
if (tags?.isNotEmpty ?? false) 'tags': tags!.join(','),
|
||||||
|
if (categories?.isNotEmpty ?? false) 'categories': categories!.join(','),
|
||||||
});
|
});
|
||||||
final List<SnPost> out = await _preloadRelatedDataInBatch(
|
final List<SnPost> out = await _preloadRelatedDataInBatch(
|
||||||
List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []),
|
List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []),
|
||||||
@ -118,12 +122,14 @@ class SnPostContentProvider {
|
|||||||
int take = 10,
|
int take = 10,
|
||||||
int offset = 0,
|
int offset = 0,
|
||||||
Iterable<String>? tags,
|
Iterable<String>? tags,
|
||||||
|
Iterable<String>? categories,
|
||||||
}) async {
|
}) async {
|
||||||
final resp = await _sn.client.get('/cgi/co/posts/search', queryParameters: {
|
final resp = await _sn.client.get('/cgi/co/posts/search', queryParameters: {
|
||||||
'take': take,
|
'take': take,
|
||||||
'offset': offset,
|
'offset': offset,
|
||||||
'probe': searchTerm,
|
'probe': searchTerm,
|
||||||
if (tags?.isNotEmpty ?? false) 'tags': tags!.join(','),
|
if (tags?.isNotEmpty ?? false) 'tags': tags!.join(','),
|
||||||
|
if (categories?.isNotEmpty ?? false) 'categories': categories!.join(','),
|
||||||
});
|
});
|
||||||
final List<SnPost> out = await _preloadRelatedDataInBatch(
|
final List<SnPost> out = await _preloadRelatedDataInBatch(
|
||||||
List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []),
|
List.from(resp.data['data']?.map((e) => SnPost.fromJson(e)) ?? []),
|
||||||
|
@ -41,8 +41,7 @@ class SnAttachmentProvider {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<SnAttachment?>> getMultiple(List<String> rids,
|
Future<List<SnAttachment?>> getMultiple(List<String> rids, {noCache = false}) async {
|
||||||
{noCache = false}) async {
|
|
||||||
final result = List<SnAttachment?>.filled(rids.length, null);
|
final result = List<SnAttachment?>.filled(rids.length, null);
|
||||||
final Map<String, int> randomMapping = {};
|
final Map<String, int> randomMapping = {};
|
||||||
for (int i = 0; i < rids.length; i++) {
|
for (int i = 0; i < rids.length; i++) {
|
||||||
@ -63,9 +62,7 @@ class SnAttachmentProvider {
|
|||||||
'id': pendingFetch.join(','),
|
'id': pendingFetch.join(','),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
final out = resp.data['data']
|
final out = resp.data['data'].map((e) => e['id'] == 0 ? null : SnAttachment.fromJson(e)).toList();
|
||||||
.map((e) => e['id'] == 0 ? null : SnAttachment.fromJson(e))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
for (final item in out) {
|
for (final item in out) {
|
||||||
if (item == null) continue;
|
if (item == null) continue;
|
||||||
@ -79,10 +76,7 @@ class SnAttachmentProvider {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Map<String, String> mimetypeOverrides = {
|
static Map<String, String> mimetypeOverrides = {'mov': 'video/quicktime', 'mp4': 'video/mp4'};
|
||||||
'mov': 'video/quicktime',
|
|
||||||
'mp4': 'video/mp4'
|
|
||||||
};
|
|
||||||
|
|
||||||
Future<SnAttachment> directUploadOne(
|
Future<SnAttachment> directUploadOne(
|
||||||
Uint8List data,
|
Uint8List data,
|
||||||
@ -93,11 +87,8 @@ class SnAttachmentProvider {
|
|||||||
Function(double progress)? onProgress,
|
Function(double progress)? onProgress,
|
||||||
}) async {
|
}) async {
|
||||||
final filePayload = MultipartFile.fromBytes(data, filename: filename);
|
final filePayload = MultipartFile.fromBytes(data, filename: filename);
|
||||||
final fileAlt = filename.contains('.')
|
final fileAlt = filename.contains('.') ? filename.substring(0, filename.lastIndexOf('.')) : filename;
|
||||||
? filename.substring(0, filename.lastIndexOf('.'))
|
final fileExt = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase();
|
||||||
: filename;
|
|
||||||
final fileExt =
|
|
||||||
filename.substring(filename.lastIndexOf('.') + 1).toLowerCase();
|
|
||||||
|
|
||||||
String? mimetypeOverride;
|
String? mimetypeOverride;
|
||||||
if (mimetype != null) {
|
if (mimetype != null) {
|
||||||
@ -133,11 +124,8 @@ class SnAttachmentProvider {
|
|||||||
Map<String, dynamic>? metadata, {
|
Map<String, dynamic>? metadata, {
|
||||||
String? mimetype,
|
String? mimetype,
|
||||||
}) async {
|
}) async {
|
||||||
final fileAlt = filename.contains('.')
|
final fileAlt = filename.contains('.') ? filename.substring(0, filename.lastIndexOf('.')) : filename;
|
||||||
? filename.substring(0, filename.lastIndexOf('.'))
|
final fileExt = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase();
|
||||||
: filename;
|
|
||||||
final fileExt =
|
|
||||||
filename.substring(filename.lastIndexOf('.') + 1).toLowerCase();
|
|
||||||
|
|
||||||
String? mimetypeOverride;
|
String? mimetypeOverride;
|
||||||
if (mimetype == null && mimetypeOverrides.keys.contains(fileExt)) {
|
if (mimetype == null && mimetypeOverrides.keys.contains(fileExt)) {
|
||||||
@ -155,10 +143,7 @@ class SnAttachmentProvider {
|
|||||||
if (mimetypeOverride != null) 'mimetype': mimetypeOverride,
|
if (mimetypeOverride != null) 'mimetype': mimetypeOverride,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (SnAttachment.fromJson(resp.data['meta']), resp.data['chunk_size'] as int);
|
||||||
SnAttachment.fromJson(resp.data['meta']),
|
|
||||||
resp.data['chunk_size'] as int
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SnAttachment> _chunkedUploadOnePart(
|
Future<SnAttachment> _chunkedUploadOnePart(
|
||||||
@ -200,24 +185,17 @@ class SnAttachmentProvider {
|
|||||||
(entry.value + 1) * chunkSize,
|
(entry.value + 1) * chunkSize,
|
||||||
await file.length(),
|
await file.length(),
|
||||||
);
|
);
|
||||||
final data = Uint8List.fromList(await file
|
final data = Uint8List.fromList(await file.openRead(beginCursor, endCursor).expand((chunk) => chunk).toList());
|
||||||
.openRead(beginCursor, endCursor)
|
|
||||||
.expand((chunk) => chunk)
|
|
||||||
.toList());
|
|
||||||
|
|
||||||
place = await _chunkedUploadOnePart(
|
place = await _chunkedUploadOnePart(
|
||||||
data,
|
data,
|
||||||
place.rid,
|
place.rid,
|
||||||
entry.key,
|
entry.key,
|
||||||
onProgress: (chunkProgress) {
|
|
||||||
final overallProgress =
|
|
||||||
(currentTask + chunkProgress) / chunks.length;
|
|
||||||
if (onProgress != null) {
|
|
||||||
onProgress(overallProgress);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final overallProgress = currentTask / chunks.length;
|
||||||
|
onProgress?.call(overallProgress);
|
||||||
|
|
||||||
currentTask++;
|
currentTask++;
|
||||||
}());
|
}());
|
||||||
}
|
}
|
||||||
|
@ -6,30 +6,34 @@ import 'dart:io';
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:dio_smart_retry/dio_smart_retry.dart';
|
import 'package:dio_smart_retry/dio_smart_retry.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:surface/providers/config.dart';
|
||||||
|
import 'package:surface/providers/widget.dart';
|
||||||
import 'package:synchronized/synchronized.dart';
|
import 'package:synchronized/synchronized.dart';
|
||||||
|
|
||||||
const kAtkStoreKey = 'nex_user_atk';
|
|
||||||
const kRtkStoreKey = 'nex_user_rtk';
|
|
||||||
|
|
||||||
const kNetworkServerDefault = 'https://api.sn.solsynth.dev';
|
|
||||||
const kNetworkServerStoreKey = 'app_server_url';
|
|
||||||
|
|
||||||
const kNetworkServerDirectory = [
|
const kNetworkServerDirectory = [
|
||||||
('Solar Network', 'https://api.sn.solsynth.dev'),
|
('Solar Network', 'https://api.sn.solsynth.dev'),
|
||||||
('Local', 'http://localhost:8001'),
|
('Local', 'http://localhost:8001'),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
Completer<String?>? _refreshCompleter;
|
||||||
|
|
||||||
class SnNetworkProvider {
|
class SnNetworkProvider {
|
||||||
late final Dio client;
|
late final Dio client;
|
||||||
|
|
||||||
late final SharedPreferences _prefs;
|
late final SharedPreferences _prefs;
|
||||||
|
late final ConfigProvider _config;
|
||||||
|
late final HomeWidgetProvider _home;
|
||||||
|
|
||||||
String? _userAgent;
|
String? _userAgent;
|
||||||
|
|
||||||
SnNetworkProvider() {
|
SnNetworkProvider(BuildContext context) {
|
||||||
|
_home = context.read<HomeWidgetProvider>();
|
||||||
|
|
||||||
client = Dio();
|
client = Dio();
|
||||||
|
|
||||||
client.interceptors.add(RetryInterceptor(
|
client.interceptors.add(RetryInterceptor(
|
||||||
@ -60,13 +64,55 @@ class SnNetworkProvider {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
SharedPreferences.getInstance().then((prefs) {
|
_config = context.read<ConfigProvider>();
|
||||||
_prefs = prefs;
|
_config.initialize().then((_) {
|
||||||
client.options.baseUrl = _prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault;
|
_prefs = _config.prefs;
|
||||||
|
client.options.baseUrl = _config.serverUrl;
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initializeUserAgent() async {
|
static Future<Dio> createOffContextClient() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final client = Dio();
|
||||||
|
client.interceptors.add(RetryInterceptor(
|
||||||
|
dio: client,
|
||||||
|
retries: 3,
|
||||||
|
retryDelays: const [
|
||||||
|
Duration(milliseconds: 300),
|
||||||
|
Duration(milliseconds: 1000),
|
||||||
|
Duration(milliseconds: 3000),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
final ua = await _getUserAgent();
|
||||||
|
client.interceptors.add(
|
||||||
|
InterceptorsWrapper(
|
||||||
|
onRequest: (
|
||||||
|
RequestOptions options,
|
||||||
|
RequestInterceptorHandler handler,
|
||||||
|
) async {
|
||||||
|
final atk = await _getFreshAtk(client, prefs.getString(kAtkStoreKey), prefs.getString(kRtkStoreKey), (atk, rtk) {
|
||||||
|
prefs.setString(kAtkStoreKey, atk);
|
||||||
|
prefs.setString(kRtkStoreKey, rtk);
|
||||||
|
});
|
||||||
|
if (atk != null) {
|
||||||
|
options.headers['Authorization'] = 'Bearer $atk';
|
||||||
|
}
|
||||||
|
options.headers['User-Agent'] = ua;
|
||||||
|
return handler.next(options);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
client.options.baseUrl = prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault;
|
||||||
|
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setConfigWithNative() async {
|
||||||
|
_home.saveWidgetData("nex_server_url", client.options.baseUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<String> _getUserAgent() async {
|
||||||
final String platformInfo;
|
final String platformInfo;
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
final deviceInfo = await DeviceInfoPlugin().webBrowserInfo;
|
final deviceInfo = await DeviceInfoPlugin().webBrowserInfo;
|
||||||
@ -92,14 +138,22 @@ class SnNetworkProvider {
|
|||||||
|
|
||||||
final packageInfo = await PackageInfo.fromPlatform();
|
final packageInfo = await PackageInfo.fromPlatform();
|
||||||
|
|
||||||
_userAgent = 'Solian/${packageInfo.version}+${packageInfo.buildNumber} ($platformInfo)';
|
return 'Solian/${packageInfo.version}+${packageInfo.buildNumber} ($platformInfo)';
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> initializeUserAgent() async {
|
||||||
|
_userAgent = await _getUserAgent();
|
||||||
}
|
}
|
||||||
|
|
||||||
final tkLock = Lock();
|
final tkLock = Lock();
|
||||||
|
|
||||||
Completer<String?>? _refreshCompleter;
|
|
||||||
|
|
||||||
Future<String?> getFreshAtk() async {
|
Future<String?> getFreshAtk() async {
|
||||||
|
return await _getFreshAtk(client, _prefs.getString(kAtkStoreKey), _prefs.getString(kRtkStoreKey), (atk, rtk) {
|
||||||
|
setTokenPair(atk, rtk);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<String?> _getFreshAtk(Dio client, String? atk, String? rtk, Function(String atk, String rtk)? onRefresh) async {
|
||||||
if (_refreshCompleter != null) {
|
if (_refreshCompleter != null) {
|
||||||
return await _refreshCompleter!.future;
|
return await _refreshCompleter!.future;
|
||||||
} else {
|
} else {
|
||||||
@ -107,7 +161,6 @@ class SnNetworkProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var atk = _prefs.getString(kAtkStoreKey);
|
|
||||||
if (atk != null) {
|
if (atk != null) {
|
||||||
final atkParts = atk.split('.');
|
final atkParts = atk.split('.');
|
||||||
if (atkParts.length != 3) {
|
if (atkParts.length != 3) {
|
||||||
@ -133,7 +186,13 @@ class SnNetworkProvider {
|
|||||||
final exp = jsonDecode(payload)['exp'];
|
final exp = jsonDecode(payload)['exp'];
|
||||||
if (exp <= DateTime.now().millisecondsSinceEpoch ~/ 1000) {
|
if (exp <= DateTime.now().millisecondsSinceEpoch ~/ 1000) {
|
||||||
log('Access token need refresh, doing it at ${DateTime.now()}');
|
log('Access token need refresh, doing it at ${DateTime.now()}');
|
||||||
atk = await refreshToken();
|
final result = await _refreshToken(client.options.baseUrl, rtk);
|
||||||
|
if (result == null) {
|
||||||
|
atk = null;
|
||||||
|
} else {
|
||||||
|
atk = result.$1;
|
||||||
|
onRefresh?.call(atk, result.$2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (atk != null) {
|
if (atk != null) {
|
||||||
@ -171,24 +230,32 @@ class SnNetworkProvider {
|
|||||||
|
|
||||||
Future<String?> refreshToken() async {
|
Future<String?> refreshToken() async {
|
||||||
final rtk = _prefs.getString(kRtkStoreKey);
|
final rtk = _prefs.getString(kRtkStoreKey);
|
||||||
|
final result = await _refreshToken(client.options.baseUrl, rtk);
|
||||||
|
if (result == null) return null;
|
||||||
|
_prefs.setString(kAtkStoreKey, result.$1);
|
||||||
|
_prefs.setString(kRtkStoreKey, result.$2);
|
||||||
|
return result.$1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<(String, String)?> _refreshToken(String baseUrl, String? rtk) async {
|
||||||
if (rtk == null) return null;
|
if (rtk == null) return null;
|
||||||
|
|
||||||
final dio = Dio();
|
final dio = Dio();
|
||||||
dio.options.baseUrl = client.options.baseUrl;
|
dio.options.baseUrl = baseUrl;
|
||||||
|
|
||||||
final resp = await dio.post('/cgi/id/auth/token', data: {
|
final resp = await dio.post('/cgi/id/auth/token', data: {
|
||||||
'grant_type': 'refresh_token',
|
'grant_type': 'refresh_token',
|
||||||
'refresh_token': rtk,
|
'refresh_token': rtk,
|
||||||
});
|
});
|
||||||
|
|
||||||
final atk = resp.data['access_token'];
|
final String atk = resp.data['access_token'];
|
||||||
final nRtk = resp.data['refresh_token'];
|
final String nRtk = resp.data['refresh_token'];
|
||||||
setTokenPair(atk, nRtk);
|
|
||||||
|
|
||||||
return atk;
|
return (atk, nRtk);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setBaseUrl(String url) {
|
void setBaseUrl(String url) {
|
||||||
|
_config.serverUrl = url;
|
||||||
client.options.baseUrl = url;
|
client.options.baseUrl = url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
136
lib/providers/special_day.dart
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:surface/providers/userinfo.dart';
|
||||||
|
|
||||||
|
// Stored as key: month, day
|
||||||
|
const Map<String, (int, int)> kSpecialDays = {
|
||||||
|
// Birthday is dynamically generated according to the user's profile
|
||||||
|
'NewYear': (1, 1),
|
||||||
|
'ValentineDay': (2, 14),
|
||||||
|
'LaborDay': (5, 1),
|
||||||
|
'MotherDay': (5, 11),
|
||||||
|
'ChildrenDay': (6, 1),
|
||||||
|
'FatherDay': (8, 8),
|
||||||
|
'Halloween': (10, 31),
|
||||||
|
'Thanksgiving': (11, 28),
|
||||||
|
'MerryXmas': (12, 25),
|
||||||
|
};
|
||||||
|
|
||||||
|
const Map<String, String> kSpecialDaysSymbol = {
|
||||||
|
'Birthday': '🎂',
|
||||||
|
'NewYear': '🎉',
|
||||||
|
'MerryXmas': '🎄',
|
||||||
|
'ValentineDay': '💑',
|
||||||
|
'LaborDay': '🏋️',
|
||||||
|
'MotherDay': '👩',
|
||||||
|
'ChildrenDay': '👶',
|
||||||
|
'FatherDay': '👨',
|
||||||
|
'Halloween': '🎃',
|
||||||
|
'Thanksgiving': '🎅',
|
||||||
|
};
|
||||||
|
|
||||||
|
class SpecialDayProvider {
|
||||||
|
late final UserProvider _user;
|
||||||
|
|
||||||
|
SpecialDayProvider(BuildContext context) {
|
||||||
|
_user = context.read<UserProvider>();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> getSpecialDays() {
|
||||||
|
final now = DateTime.now().toLocal();
|
||||||
|
final birthday = _user.user?.profile?.birthday?.toLocal();
|
||||||
|
final isBirthday = birthday != null && birthday.day == now.day && birthday.month == now.month;
|
||||||
|
|
||||||
|
return [
|
||||||
|
if (isBirthday) 'Birthday',
|
||||||
|
...kSpecialDays.keys.where(
|
||||||
|
(key) => kSpecialDays[key]!.$1 == now.month && kSpecialDays[key]!.$2 == now.day,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
(String, DateTime)? getLastSpecialDay() {
|
||||||
|
final now = DateTime.now().toLocal();
|
||||||
|
final birthday = _user.user?.profile?.birthday?.toLocal();
|
||||||
|
|
||||||
|
final Map<String, (int, int)> specialDays = {
|
||||||
|
if (birthday != null) 'Birthday': (birthday.month, birthday.day),
|
||||||
|
...kSpecialDays,
|
||||||
|
};
|
||||||
|
|
||||||
|
DateTime? lastDate;
|
||||||
|
String? lastEvent;
|
||||||
|
|
||||||
|
for (final entry in specialDays.entries) {
|
||||||
|
final eventName = entry.key;
|
||||||
|
final (month, day) = entry.value;
|
||||||
|
|
||||||
|
var specialDayThisYear = DateTime(now.year, month, day);
|
||||||
|
var specialDayLastYear = DateTime(now.year - 1, month, day);
|
||||||
|
|
||||||
|
if (specialDayThisYear.isBefore(now)) {
|
||||||
|
if (lastDate == null || specialDayThisYear.isAfter(lastDate)) {
|
||||||
|
lastDate = specialDayThisYear;
|
||||||
|
lastEvent = eventName;
|
||||||
|
}
|
||||||
|
} else if (specialDayLastYear.isBefore(now)) {
|
||||||
|
if (lastDate == null || specialDayLastYear.isAfter(lastDate)) {
|
||||||
|
lastDate = specialDayLastYear;
|
||||||
|
lastEvent = eventName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastEvent != null && lastDate != null) {
|
||||||
|
return (lastEvent, lastDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
(String, DateTime)? getNextSpecialDay() {
|
||||||
|
final now = DateTime.now().toLocal();
|
||||||
|
final birthday = _user.user?.profile?.birthday?.toLocal();
|
||||||
|
|
||||||
|
// Stored as key: month, day
|
||||||
|
final Map<String, (int, int)> specialDays = {
|
||||||
|
if (birthday != null) 'Birthday': (birthday.month, birthday.day),
|
||||||
|
...kSpecialDays,
|
||||||
|
};
|
||||||
|
|
||||||
|
DateTime? closestDate;
|
||||||
|
String? closestEvent;
|
||||||
|
|
||||||
|
for (final entry in specialDays.entries) {
|
||||||
|
final eventName = entry.key;
|
||||||
|
final (month, day) = entry.value;
|
||||||
|
|
||||||
|
// Calculate the special day's DateTime in the current year
|
||||||
|
var specialDay = DateTime(now.year, month, day);
|
||||||
|
|
||||||
|
// If the special day has already passed this year, consider it for the next year
|
||||||
|
if (specialDay.isBefore(now)) {
|
||||||
|
specialDay = DateTime(now.year + 1, month, day);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this special day is closer than the previously found one
|
||||||
|
if (closestDate == null || specialDay.isBefore(closestDate)) {
|
||||||
|
closestDate = specialDay;
|
||||||
|
closestEvent = eventName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closestEvent != null && closestDate != null) {
|
||||||
|
return (closestEvent, closestDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No special day found
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
double getSpecialDayProgress(DateTime last, DateTime next) {
|
||||||
|
final totalDuration = next.difference(last).inSeconds.toDouble();
|
||||||
|
final elapsedDuration = DateTime.now().difference(last).inSeconds.toDouble();
|
||||||
|
return (elapsedDuration / totalDuration).clamp(0.0, 1.0);
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:surface/theme.dart';
|
import 'package:surface/theme.dart';
|
||||||
|
|
||||||
@ -11,8 +13,8 @@ class ThemeProvider extends ChangeNotifier {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void reloadTheme({bool? useMaterial3}) {
|
void reloadTheme({Color? seedColorOverride, bool? useMaterial3}) {
|
||||||
createAppThemeSet().then((value) {
|
createAppThemeSet(seedColorOverride: seedColorOverride, useMaterial3: useMaterial3).then((value) {
|
||||||
theme = value;
|
theme = value;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
});
|
});
|
||||||
|
@ -3,7 +3,9 @@ import 'dart:developer';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:surface/providers/config.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/providers/widget.dart';
|
||||||
import 'package:surface/types/account.dart';
|
import 'package:surface/types/account.dart';
|
||||||
|
|
||||||
class UserProvider extends ChangeNotifier {
|
class UserProvider extends ChangeNotifier {
|
||||||
@ -11,19 +13,22 @@ class UserProvider extends ChangeNotifier {
|
|||||||
SnAccount? user;
|
SnAccount? user;
|
||||||
|
|
||||||
late final SnNetworkProvider _sn;
|
late final SnNetworkProvider _sn;
|
||||||
|
late final HomeWidgetProvider _home;
|
||||||
|
late final ConfigProvider _config;
|
||||||
|
|
||||||
|
UserProvider(BuildContext context) {
|
||||||
|
_sn = context.read<SnNetworkProvider>();
|
||||||
|
_home = context.read<HomeWidgetProvider>();
|
||||||
|
_config = context.read<ConfigProvider>();
|
||||||
|
}
|
||||||
|
|
||||||
Future<String?> get atk async {
|
Future<String?> get atk async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
return prefs.getString(kAtkStoreKey);
|
return prefs.getString(kAtkStoreKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
UserProvider(BuildContext context) {
|
|
||||||
_sn = context.read<SnNetworkProvider>();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final value = _config.prefs.getString(kAtkStoreKey);
|
||||||
final value = prefs.getString(kAtkStoreKey);
|
|
||||||
isAuthorized = value != null;
|
isAuthorized = value != null;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
refreshUser().then((value) {
|
refreshUser().then((value) {
|
||||||
|
@ -26,6 +26,7 @@ class WebSocketProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> tryConnect() async {
|
Future<void> tryConnect() async {
|
||||||
|
if (isConnected) return;
|
||||||
if (!_ua.isAuthorized) return;
|
if (!_ua.isAuthorized) return;
|
||||||
|
|
||||||
log('[WebSocket] Connecting to the server...');
|
log('[WebSocket] Connecting to the server...');
|
||||||
@ -76,6 +77,7 @@ class WebSocketProvider extends ChangeNotifier {
|
|||||||
if (conn != null) {
|
if (conn != null) {
|
||||||
conn!.sink.close();
|
conn!.sink.close();
|
||||||
}
|
}
|
||||||
|
conn = null;
|
||||||
isConnected = false;
|
isConnected = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
60
lib/providers/widget.dart
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:home_widget/home_widget.dart';
|
||||||
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/types/post.dart';
|
||||||
|
|
||||||
|
class HomeWidgetProvider {
|
||||||
|
HomeWidgetProvider(BuildContext context);
|
||||||
|
|
||||||
|
Future<void> initialize() async {
|
||||||
|
if (kIsWeb || !(Platform.isAndroid || Platform.isIOS)) return;
|
||||||
|
if (Platform.isIOS) {
|
||||||
|
await HomeWidget.setAppGroupId("group.solsynth.solian");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> saveWidgetData(String id, dynamic data, {bool update = true}) async {
|
||||||
|
if (kIsWeb || !(Platform.isAndroid || Platform.isIOS)) return;
|
||||||
|
await HomeWidget.saveWidgetData(id, jsonEncode(data));
|
||||||
|
if (update) await updateWidget();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateWidget() async {
|
||||||
|
if (kIsWeb || !(Platform.isAndroid || Platform.isIOS)) return;
|
||||||
|
if (Platform.isIOS) {
|
||||||
|
const widgets = ["SolarRandomPostWidget", "SolarCheckInWidget"];
|
||||||
|
for (final widget in widgets) {
|
||||||
|
await HomeWidget.updateWidget(
|
||||||
|
name: widget,
|
||||||
|
iOSName: widget,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (Platform.isAndroid) {
|
||||||
|
const widgets = ["RandomPostWidget", "CheckInWidget"];
|
||||||
|
for (final widget in widgets) {
|
||||||
|
await HomeWidget.updateWidget(
|
||||||
|
androidName: "${widget}Receiver",
|
||||||
|
qualifiedAndroidName: "dev.solsynth.solian.widgets.${widget}Receiver",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> widgetUpdateRandomPost() async {
|
||||||
|
final snc = await SnNetworkProvider.createOffContextClient();
|
||||||
|
final resp = await snc.get('/cgi/co/recommendations/shuffle?take=1');
|
||||||
|
final post = SnPost.fromJson(resp.data['data'][0]);
|
||||||
|
await HomeWidget.saveWidgetData("int_random_post", jsonEncode(post.toJson()));
|
||||||
|
await HomeWidget.updateWidget(
|
||||||
|
name: "SolarRandomPostWidget",
|
||||||
|
iOSName: "SolarRandomPostWidget",
|
||||||
|
androidName: "RandomPostWidgetReceiver",
|
||||||
|
qualifiedAndroidName: "dev.solsynth.solian.widgets.RandomPostWidgetReceiver",
|
||||||
|
);
|
||||||
|
}
|
@ -28,6 +28,7 @@ import 'package:surface/screens/realm.dart';
|
|||||||
import 'package:surface/screens/realm/manage.dart';
|
import 'package:surface/screens/realm/manage.dart';
|
||||||
import 'package:surface/screens/realm/realm_detail.dart';
|
import 'package:surface/screens/realm/realm_detail.dart';
|
||||||
import 'package:surface/screens/settings.dart';
|
import 'package:surface/screens/settings.dart';
|
||||||
|
import 'package:surface/screens/sharing.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/about.dart';
|
import 'package:surface/widgets/about.dart';
|
||||||
import 'package:surface/widgets/navigation/app_background.dart';
|
import 'package:surface/widgets/navigation/app_background.dart';
|
||||||
@ -69,14 +70,18 @@ final _appRoutes = [
|
|||||||
postRepostId: int.tryParse(
|
postRepostId: int.tryParse(
|
||||||
state.uri.queryParameters['reposting'] ?? '',
|
state.uri.queryParameters['reposting'] ?? '',
|
||||||
),
|
),
|
||||||
|
extraProps: state.extra as PostEditorExtraProps?,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/search',
|
path: '/search',
|
||||||
name: 'postSearch',
|
name: 'postSearch',
|
||||||
builder: (context, state) => const AppBackground(
|
builder: (context, state) => AppBackground(
|
||||||
child: PostSearchScreen(),
|
child: PostSearchScreen(
|
||||||
|
initialTags: state.uri.queryParameters['tags']?.split(','),
|
||||||
|
initialCategories: state.uri.queryParameters['categories']?.split(','),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
@ -315,7 +320,9 @@ final appRouter = GoRouter(
|
|||||||
routes: [
|
routes: [
|
||||||
ShellRoute(
|
ShellRoute(
|
||||||
routes: _appRoutes,
|
routes: _appRoutes,
|
||||||
builder: (context, state, child) => AppRootScaffold(body: child),
|
builder: (context, state, child) => AppRootScaffold(
|
||||||
|
body: AppSharingListener(child: child),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
@ -9,10 +10,12 @@ import 'package:material_symbols_icons/symbols.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:relative_time/relative_time.dart';
|
import 'package:relative_time/relative_time.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/providers/experience.dart';
|
||||||
import 'package:surface/providers/relationship.dart';
|
import 'package:surface/providers/relationship.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/screens/abuse_report.dart';
|
import 'package:surface/screens/abuse_report.dart';
|
||||||
import 'package:surface/types/account.dart';
|
import 'package:surface/types/account.dart';
|
||||||
|
import 'package:surface/types/check_in.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
@ -61,6 +64,19 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<SnCheckInRecord>> _getCheckInRecords() async {
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/id/users/${widget.name}/check-in?take=14');
|
||||||
|
return List.from(
|
||||||
|
resp.data['data']?.map((x) => SnCheckInRecord.fromJson(x)) ?? [],
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
if (mounted) context.showErrorDialog(err);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SnAccountStatusInfo? _status;
|
SnAccountStatusInfo? _status;
|
||||||
|
|
||||||
Future<void> _fetchStatus() async {
|
Future<void> _fetchStatus() async {
|
||||||
@ -228,7 +244,13 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverAppBar(
|
Theme(
|
||||||
|
data: Theme.of(context).copyWith(
|
||||||
|
appBarTheme: Theme.of(context).appBarTheme.copyWith(
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: SliverAppBar(
|
||||||
expandedHeight: _appBarHeight,
|
expandedHeight: _appBarHeight,
|
||||||
title: _account == null
|
title: _account == null
|
||||||
? Text('loading').tr()
|
? Text('loading').tr()
|
||||||
@ -238,7 +260,7 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
text: _account!.nick,
|
text: _account!.nick,
|
||||||
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
color: Colors.white,
|
||||||
shadows: labelShadows,
|
shadows: labelShadows,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -246,7 +268,7 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
text: '@${_account!.name}',
|
text: '@${_account!.name}',
|
||||||
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
style: Theme.of(context).textTheme.bodySmall!.copyWith(
|
||||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
color: Colors.white,
|
||||||
shadows: labelShadows,
|
shadows: labelShadows,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -288,6 +310,7 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
if (_account != null)
|
if (_account != null)
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -430,6 +453,7 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.calendar_add_on),
|
const Icon(Symbols.calendar_add_on),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
@ -437,6 +461,7 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.cake),
|
const Icon(Symbols.cake),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
@ -450,6 +475,7 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Symbols.identity_platform),
|
const Icon(Symbols.identity_platform),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
@ -459,6 +485,26 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
).opacity(0.8),
|
).opacity(0.8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.star),
|
||||||
|
const Gap(8),
|
||||||
|
Text('Lv${getLevelFromExp(_account?.profile?.experience ?? 0)}'),
|
||||||
|
const Gap(8),
|
||||||
|
Text(calcLevelUpProgressLevel(_account?.profile?.experience ?? 0)).fontSize(11).opacity(0.5),
|
||||||
|
const Gap(8),
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
constraints: const BoxConstraints(maxWidth: 160),
|
||||||
|
child: LinearProgressIndicator(
|
||||||
|
value: calcLevelUpProgress(_account?.profile?.experience ?? 0),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
|
).alignment(Alignment.centerLeft),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 8),
|
).padding(horizontal: 8),
|
||||||
],
|
],
|
||||||
@ -466,6 +512,27 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
),
|
),
|
||||||
SliverToBoxAdapter(child: const Divider()),
|
SliverToBoxAdapter(child: const Divider()),
|
||||||
const SliverGap(12),
|
const SliverGap(12),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: FutureBuilder<List<SnCheckInRecord>>(
|
||||||
|
future: _getCheckInRecords(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (!snapshot.hasData) return const SizedBox.shrink();
|
||||||
|
final records = snapshot.data!;
|
||||||
|
return SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 240,
|
||||||
|
child: CheckInRecordChart(records: records),
|
||||||
|
).padding(
|
||||||
|
right: 24,
|
||||||
|
left: 16,
|
||||||
|
top: 12,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SliverGap(12),
|
||||||
|
SliverToBoxAdapter(child: const Divider()),
|
||||||
|
const SliverGap(12),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@ -534,3 +601,105 @@ class _UserScreenState extends State<UserScreen> with SingleTickerProviderStateM
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CheckInRecordChart extends StatelessWidget {
|
||||||
|
const CheckInRecordChart({
|
||||||
|
super.key,
|
||||||
|
required this.records,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<SnCheckInRecord> records;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return LineChart(
|
||||||
|
LineChartData(
|
||||||
|
lineBarsData: [
|
||||||
|
LineChartBarData(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
belowBarData: BarAreaData(
|
||||||
|
show: true,
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: List.filled(
|
||||||
|
records.length,
|
||||||
|
Theme.of(context).colorScheme.primary.withOpacity(0.3),
|
||||||
|
).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
spots: records
|
||||||
|
.map(
|
||||||
|
(x) => FlSpot(
|
||||||
|
x.createdAt
|
||||||
|
.copyWith(
|
||||||
|
hour: 0,
|
||||||
|
minute: 0,
|
||||||
|
second: 0,
|
||||||
|
millisecond: 0,
|
||||||
|
microsecond: 0,
|
||||||
|
)
|
||||||
|
.millisecondsSinceEpoch
|
||||||
|
.toDouble(),
|
||||||
|
x.resultTier.toDouble(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
lineTouchData: LineTouchData(
|
||||||
|
touchTooltipData: LineTouchTooltipData(
|
||||||
|
getTooltipItems: (spots) => spots
|
||||||
|
.map(
|
||||||
|
(spot) => LineTooltipItem(
|
||||||
|
'${kCheckInResultTierSymbols[spot.y.toInt()]}\n${DateFormat('MM/dd').format(DateTime.fromMillisecondsSinceEpoch(spot.x.toInt()))}',
|
||||||
|
TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
getTooltipColor: (_) => Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
titlesData: FlTitlesData(
|
||||||
|
topTitles: const AxisTitles(
|
||||||
|
sideTitles: SideTitles(showTitles: false),
|
||||||
|
),
|
||||||
|
rightTitles: const AxisTitles(
|
||||||
|
sideTitles: SideTitles(showTitles: false),
|
||||||
|
),
|
||||||
|
leftTitles: AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
showTitles: true,
|
||||||
|
reservedSize: 40,
|
||||||
|
interval: 1,
|
||||||
|
getTitlesWidget: (value, _) => Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: Text(
|
||||||
|
kCheckInResultTierSymbols[value.toInt()],
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
).padding(right: 8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
bottomTitles: AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
showTitles: true,
|
||||||
|
reservedSize: 28,
|
||||||
|
interval: 86400000,
|
||||||
|
getTitlesWidget: (value, _) => Text(
|
||||||
|
DateFormat('dd').format(
|
||||||
|
DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
value.toInt(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
).padding(top: 8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
gridData: const FlGridData(show: false),
|
||||||
|
borderData: FlBorderData(show: false),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -158,7 +158,7 @@ class _ChatRoomScreenState extends State<ChatRoomScreen> {
|
|||||||
GoRouter.of(context).pushNamed(
|
GoRouter.of(context).pushNamed(
|
||||||
'chatCallRoom',
|
'chatCallRoom',
|
||||||
pathParameters: {
|
pathParameters: {
|
||||||
'scope': _channel!.realm!.alias,
|
'scope': _channel!.realm?.alias ?? 'global',
|
||||||
'alias': _channel!.alias,
|
'alias': _channel!.alias,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -5,12 +5,28 @@ import 'package:gap/gap.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/providers/post.dart';
|
import 'package:surface/providers/post.dart';
|
||||||
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/app_bar_leading.dart';
|
import 'package:surface/widgets/app_bar_leading.dart';
|
||||||
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:surface/widgets/post/post_item.dart';
|
import 'package:surface/widgets/post/post_item.dart';
|
||||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
|
|
||||||
|
const Map<String, IconData> kCategoryIcons = {
|
||||||
|
'technology': Symbols.tools_wrench,
|
||||||
|
'gaming': Symbols.gamepad,
|
||||||
|
'life': Symbols.nightlife,
|
||||||
|
'arts': Symbols.format_paint,
|
||||||
|
'sports': Symbols.sports_soccer,
|
||||||
|
'music': Symbols.music_note,
|
||||||
|
'news': Symbols.newspaper,
|
||||||
|
'knowledge': Symbols.library_books,
|
||||||
|
'literature': Symbols.book,
|
||||||
|
'funny': Symbols.attractions,
|
||||||
|
};
|
||||||
|
|
||||||
class ExploreScreen extends StatefulWidget {
|
class ExploreScreen extends StatefulWidget {
|
||||||
const ExploreScreen({super.key});
|
const ExploreScreen({super.key});
|
||||||
|
|
||||||
@ -24,15 +40,34 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
bool _isBusy = true;
|
bool _isBusy = true;
|
||||||
|
|
||||||
final List<SnPost> _posts = List.empty(growable: true);
|
final List<SnPost> _posts = List.empty(growable: true);
|
||||||
|
final List<SnPostCategory> _categories = List.empty(growable: true);
|
||||||
int? _postCount;
|
int? _postCount;
|
||||||
|
|
||||||
|
String? _selectedCategory;
|
||||||
|
|
||||||
|
Future<void> _fetchCategories() async {
|
||||||
|
_categories.clear();
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/co/categories?take=100');
|
||||||
|
_categories.addAll(resp.data.map((e) => SnPostCategory.fromJson(e)).cast<SnPostCategory>() ?? []);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _fetchPosts() async {
|
Future<void> _fetchPosts() async {
|
||||||
if (_postCount != null && _posts.length >= _postCount!) return;
|
if (_postCount != null && _posts.length >= _postCount!) return;
|
||||||
|
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
final pt = context.read<SnPostContentProvider>();
|
final pt = context.read<SnPostContentProvider>();
|
||||||
final result = await pt.listPosts(take: 10, offset: _posts.length);
|
final result = await pt.listPosts(
|
||||||
|
take: 10,
|
||||||
|
offset: _posts.length,
|
||||||
|
categories: _selectedCategory != null ? [_selectedCategory!] : null,
|
||||||
|
);
|
||||||
final out = result.$1;
|
final out = result.$1;
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
@ -43,10 +78,17 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
if (mounted) setState(() => _isBusy = false);
|
if (mounted) setState(() => _isBusy = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _refreshPosts() {
|
||||||
|
_postCount = null;
|
||||||
|
_posts.clear();
|
||||||
|
return _fetchPosts();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_fetchPosts();
|
_fetchPosts();
|
||||||
|
_fetchCategories();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -59,27 +101,20 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
type: ExpandableFabType.up,
|
type: ExpandableFabType.up,
|
||||||
childrenAnimation: ExpandableFabAnimation.none,
|
childrenAnimation: ExpandableFabAnimation.none,
|
||||||
overlayStyle: ExpandableFabOverlayStyle(
|
overlayStyle: ExpandableFabOverlayStyle(
|
||||||
color: Theme.of(context)
|
color: Theme.of(context).colorScheme.surface.withAlpha((255 * 0.5).round()),
|
||||||
.colorScheme
|
|
||||||
.surface
|
|
||||||
.withAlpha((255 * 0.5).round()),
|
|
||||||
),
|
),
|
||||||
openButtonBuilder: RotateFloatingActionButtonBuilder(
|
openButtonBuilder: RotateFloatingActionButtonBuilder(
|
||||||
child: const Icon(Symbols.add, size: 28),
|
child: const Icon(Symbols.add, size: 28),
|
||||||
fabSize: ExpandableFabSize.regular,
|
fabSize: ExpandableFabSize.regular,
|
||||||
foregroundColor:
|
foregroundColor: Theme.of(context).floatingActionButtonTheme.foregroundColor,
|
||||||
Theme.of(context).floatingActionButtonTheme.foregroundColor,
|
backgroundColor: Theme.of(context).floatingActionButtonTheme.backgroundColor,
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).floatingActionButtonTheme.backgroundColor,
|
|
||||||
shape: const CircleBorder(),
|
shape: const CircleBorder(),
|
||||||
),
|
),
|
||||||
closeButtonBuilder: DefaultFloatingActionButtonBuilder(
|
closeButtonBuilder: DefaultFloatingActionButtonBuilder(
|
||||||
child: const Icon(Symbols.close, size: 28),
|
child: const Icon(Symbols.close, size: 28),
|
||||||
fabSize: ExpandableFabSize.regular,
|
fabSize: ExpandableFabSize.regular,
|
||||||
foregroundColor:
|
foregroundColor: Theme.of(context).floatingActionButtonTheme.foregroundColor,
|
||||||
Theme.of(context).floatingActionButtonTheme.foregroundColor,
|
backgroundColor: Theme.of(context).floatingActionButtonTheme.backgroundColor,
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).floatingActionButtonTheme.backgroundColor,
|
|
||||||
shape: const CircleBorder(),
|
shape: const CircleBorder(),
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
@ -95,8 +130,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
'mode': 'stories',
|
'mode': 'stories',
|
||||||
}).then((value) {
|
}).then((value) {
|
||||||
if (value == true) {
|
if (value == true) {
|
||||||
_posts.clear();
|
_refreshPosts();
|
||||||
_fetchPosts();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
_fabKey.currentState!.toggle();
|
_fabKey.currentState!.toggle();
|
||||||
@ -117,8 +151,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
'mode': 'articles',
|
'mode': 'articles',
|
||||||
}).then((value) {
|
}).then((value) {
|
||||||
if (value == true) {
|
if (value == true) {
|
||||||
_posts.clear();
|
_refreshPosts();
|
||||||
_fetchPosts();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
_fabKey.currentState!.toggle();
|
_fabKey.currentState!.toggle();
|
||||||
@ -131,10 +164,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
),
|
),
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
displacement: 40 + MediaQuery.of(context).padding.top,
|
displacement: 40 + MediaQuery.of(context).padding.top,
|
||||||
onRefresh: () {
|
onRefresh: () => _refreshPosts(),
|
||||||
_posts.clear();
|
|
||||||
return _fetchPosts();
|
|
||||||
},
|
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverAppBar(
|
SliverAppBar(
|
||||||
@ -151,6 +181,34 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
],
|
],
|
||||||
|
bottom: PreferredSize(
|
||||||
|
preferredSize: const Size.fromHeight(50),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 50,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
padding: const EdgeInsets.only(left: 8, right: 8, bottom: 12),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: _categories.map((ele) {
|
||||||
|
return StyledWidget(ChoiceChip(
|
||||||
|
avatar: Icon(kCategoryIcons[ele.alias] ?? Symbols.question_mark),
|
||||||
|
label: Text(
|
||||||
|
'postCategory${ele.alias.capitalize()}'.trExists()
|
||||||
|
? 'postCategory${ele.alias.capitalize()}'.tr()
|
||||||
|
: ele.name,
|
||||||
|
),
|
||||||
|
selected: _selectedCategory == ele.alias,
|
||||||
|
onSelected: (value) {
|
||||||
|
_selectedCategory = value ? ele.alias : null;
|
||||||
|
_refreshPosts();
|
||||||
|
},
|
||||||
|
)).padding(horizontal: 4);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SliverInfiniteList(
|
SliverInfiniteList(
|
||||||
itemCount: _posts.length,
|
itemCount: _posts.length,
|
||||||
@ -167,8 +225,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
setState(() => _posts[idx] = data);
|
setState(() => _posts[idx] = data);
|
||||||
},
|
},
|
||||||
onDeleted: () {
|
onDeleted: () {
|
||||||
_posts.clear();
|
_refreshPosts();
|
||||||
_fetchPosts();
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
@ -1,18 +1,25 @@
|
|||||||
|
import 'dart:io';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter_app_update/flutter_app_update.dart';
|
||||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:relative_time/relative_time.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:surface/providers/config.dart';
|
||||||
import 'package:surface/providers/post.dart';
|
import 'package:surface/providers/post.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/providers/special_day.dart';
|
||||||
import 'package:surface/providers/userinfo.dart';
|
import 'package:surface/providers/userinfo.dart';
|
||||||
|
import 'package:surface/providers/widget.dart';
|
||||||
import 'package:surface/types/check_in.dart';
|
import 'package:surface/types/check_in.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/app_bar_leading.dart';
|
import 'package:surface/widgets/app_bar_leading.dart';
|
||||||
@ -74,7 +81,8 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: constraints.maxWidth > 640 ? MainAxisAlignment.center : MainAxisAlignment.start,
|
mainAxisAlignment: constraints.maxWidth > 640 ? MainAxisAlignment.center : MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_HomeDashSpecialDayWidget().padding(top: 8, horizontal: 8),
|
_HomeDashUpdateWidget(padding: const EdgeInsets.only(bottom: 8, left: 8, right: 8)),
|
||||||
|
_HomeDashSpecialDayWidget().padding(horizontal: 8),
|
||||||
StaggeredGrid.extent(
|
StaggeredGrid.extent(
|
||||||
maxCrossAxisExtent: 280,
|
maxCrossAxisExtent: 280,
|
||||||
mainAxisSpacing: 8,
|
mainAxisSpacing: 8,
|
||||||
@ -98,26 +106,111 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _HomeDashUpdateWidget extends StatelessWidget {
|
||||||
|
final EdgeInsets? padding;
|
||||||
|
|
||||||
|
const _HomeDashUpdateWidget({super.key, this.padding});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final config = context.watch<ConfigProvider>();
|
||||||
|
|
||||||
|
return ListenableBuilder(
|
||||||
|
listenable: config,
|
||||||
|
builder: (context, _) {
|
||||||
|
if (config.updatableVersion != null) {
|
||||||
|
return Container(
|
||||||
|
padding: padding,
|
||||||
|
child: Card(
|
||||||
|
child: ListTile(
|
||||||
|
leading: Icon(Symbols.update),
|
||||||
|
title: Text('updateAvailable').tr(),
|
||||||
|
subtitle: Text(config.updatableVersion!),
|
||||||
|
trailing: (kIsWeb || Platform.isWindows || Platform.isLinux)
|
||||||
|
? null
|
||||||
|
: IconButton(
|
||||||
|
icon: const Icon(Symbols.arrow_right_alt),
|
||||||
|
onPressed: () {
|
||||||
|
final model = UpdateModel(
|
||||||
|
'https://files.solsynth.dev/d/production01/solian/app-arm64-v8a-release.apk',
|
||||||
|
'solian-app-release-${config.updatableVersion!}.apk',
|
||||||
|
'ic_launcher',
|
||||||
|
'https://apps.apple.com/us/app/solian/id6499032345',
|
||||||
|
);
|
||||||
|
AzhonAppUpdate.update(model);
|
||||||
|
context.showSnackbar('updateOngoing'.tr());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SizedBox.shrink();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _HomeDashSpecialDayWidget extends StatelessWidget {
|
class _HomeDashSpecialDayWidget extends StatelessWidget {
|
||||||
const _HomeDashSpecialDayWidget({super.key});
|
const _HomeDashSpecialDayWidget({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final ua = context.watch<UserProvider>();
|
final ua = context.watch<UserProvider>();
|
||||||
final today = DateTime.now();
|
final dayz = context.watch<SpecialDayProvider>();
|
||||||
final birthday = ua.user?.profile?.birthday?.toLocal();
|
|
||||||
final isBirthday = birthday != null && birthday.day == today.day && birthday.month == today.month;
|
final days = dayz.getSpecialDays();
|
||||||
|
|
||||||
|
if (days.isNotEmpty) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
spacing: 8,
|
||||||
if (isBirthday)
|
children: days.map((ele) {
|
||||||
Card(
|
return Card(
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
leading: Text('🎂').fontSize(24),
|
leading: Text(kSpecialDaysSymbol[ele] ?? '🎉').fontSize(24),
|
||||||
title: Text('happyBirthday').tr(args: [ua.user?.nick ?? 'user']),
|
title: Text('celebrate$ele').tr(args: [ua.user?.nick ?? 'user']),
|
||||||
|
subtitle: Text(
|
||||||
|
DateFormat('y/M/d').format(DateTime.now().copyWith(
|
||||||
|
month: kSpecialDays[ele]!.$1,
|
||||||
|
day: kSpecialDays[ele]!.$2,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).padding(bottom: 8);
|
||||||
|
}).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
final nextOne = dayz.getNextSpecialDay();
|
||||||
|
final lastOne = dayz.getLastSpecialDay();
|
||||||
|
|
||||||
|
if (nextOne != null && lastOne != null) {
|
||||||
|
var (name, date) = nextOne;
|
||||||
|
date = date.add(Duration(days: 1));
|
||||||
|
final progress = dayz.getSpecialDayProgress(lastOne.$2, date);
|
||||||
|
final diff = nextOne.$2.add(-const Duration(days: 1)).difference(lastOne.$2);
|
||||||
|
return Card(
|
||||||
|
child: ListTile(
|
||||||
|
leading: Text(kSpecialDaysSymbol[name] ?? '🎉').fontSize(24),
|
||||||
|
title: Text('pending$name').tr(args: [RelativeTime(context).format(date)]),
|
||||||
|
subtitle: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text('${diff.inDays}d · ${(progress * 100).toStringAsFixed(2)}%'),
|
||||||
|
const Gap(8),
|
||||||
|
Expanded(
|
||||||
|
child: LinearProgressIndicator(
|
||||||
|
value: progress,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
).padding(bottom: 8),
|
|
||||||
],
|
],
|
||||||
);
|
),
|
||||||
|
),
|
||||||
|
).padding(bottom: 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,8 +233,10 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> {
|
|||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final home = context.read<HomeWidgetProvider>();
|
||||||
final resp = await sn.client.get('/cgi/id/check-in/today');
|
final resp = await sn.client.get('/cgi/id/check-in/today');
|
||||||
_todayRecord = SnCheckInRecord.fromJson(resp.data);
|
_todayRecord = SnCheckInRecord.fromJson(resp.data);
|
||||||
|
await home.saveWidgetData('pas_check_in_record', _todayRecord!.toJson());
|
||||||
} finally {
|
} finally {
|
||||||
setState(() => _isBusy = false);
|
setState(() => _isBusy = false);
|
||||||
}
|
}
|
||||||
@ -151,8 +246,10 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> {
|
|||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final home = context.read<HomeWidgetProvider>();
|
||||||
final resp = await sn.client.post('/cgi/id/check-in');
|
final resp = await sn.client.post('/cgi/id/check-in');
|
||||||
_todayRecord = SnCheckInRecord.fromJson(resp.data);
|
_todayRecord = SnCheckInRecord.fromJson(resp.data);
|
||||||
|
await home.saveWidgetData('pas_check_in_record', _todayRecord!.toJson());
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
@ -171,7 +268,7 @@ class _HomeDashCheckInWidgetState extends State<_HomeDashCheckInWidget> {
|
|||||||
Text(
|
Text(
|
||||||
prefix.tr(args: ['$prefix$pos'.tr()]),
|
prefix.tr(args: ['$prefix$pos'.tr()]),
|
||||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.bold),
|
style: Theme.of(context).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.bold),
|
||||||
).tr(),
|
),
|
||||||
Text(
|
Text(
|
||||||
'$prefix${pos}Description',
|
'$prefix${pos}Description',
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
@ -13,6 +13,7 @@ import 'package:material_symbols_icons/symbols.dart';
|
|||||||
import 'package:pasteboard/pasteboard.dart';
|
import 'package:pasteboard/pasteboard.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/controllers/post_write_controller.dart';
|
import 'package:surface/controllers/post_write_controller.dart';
|
||||||
|
import 'package:surface/providers/config.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
@ -23,11 +24,26 @@ import 'package:surface/widgets/post/post_meta_editor.dart';
|
|||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class PostEditorExtraProps {
|
||||||
|
final String? text;
|
||||||
|
final String? title;
|
||||||
|
final String? description;
|
||||||
|
final List<PostWriteMedia>? attachments;
|
||||||
|
|
||||||
|
const PostEditorExtraProps({
|
||||||
|
this.text,
|
||||||
|
this.title,
|
||||||
|
this.description,
|
||||||
|
this.attachments,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
class PostEditorScreen extends StatefulWidget {
|
class PostEditorScreen extends StatefulWidget {
|
||||||
final String mode;
|
final String mode;
|
||||||
final int? postEditId;
|
final int? postEditId;
|
||||||
final int? postReplyId;
|
final int? postReplyId;
|
||||||
final int? postRepostId;
|
final int? postRepostId;
|
||||||
|
final PostEditorExtraProps? extraProps;
|
||||||
|
|
||||||
const PostEditorScreen({
|
const PostEditorScreen({
|
||||||
super.key,
|
super.key,
|
||||||
@ -35,6 +51,7 @@ class PostEditorScreen extends StatefulWidget {
|
|||||||
required this.postEditId,
|
required this.postEditId,
|
||||||
required this.postReplyId,
|
required this.postReplyId,
|
||||||
required this.postRepostId,
|
required this.postRepostId,
|
||||||
|
this.extraProps,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -55,11 +72,14 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final config = context.read<ConfigProvider>();
|
||||||
final resp = await sn.client.get('/cgi/co/publishers/me');
|
final resp = await sn.client.get('/cgi/co/publishers/me');
|
||||||
_publishers = List<SnPublisher>.from(
|
_publishers = List<SnPublisher>.from(
|
||||||
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [],
|
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [],
|
||||||
);
|
);
|
||||||
_writeController.setPublisher(_publishers?.firstOrNull);
|
final beforeId = config.prefs.getInt('int_last_publisher_id');
|
||||||
|
_writeController
|
||||||
|
.setPublisher(_publishers?.where((ele) => ele.id == beforeId).firstOrNull ?? _publishers?.firstOrNull);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
@ -130,6 +150,12 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
replying: widget.postReplyId,
|
replying: widget.postReplyId,
|
||||||
reposting: widget.postRepostId,
|
reposting: widget.postRepostId,
|
||||||
);
|
);
|
||||||
|
if (widget.extraProps != null) {
|
||||||
|
_writeController.contentController.text = widget.extraProps!.text ?? '';
|
||||||
|
_writeController.titleController.text = widget.extraProps!.title ?? '';
|
||||||
|
_writeController.descriptionController.text = widget.extraProps!.description ?? '';
|
||||||
|
_writeController.addAttachments(widget.extraProps!.attachments ?? []);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -243,6 +269,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
_writeController.setPublisher(value);
|
_writeController.setPublisher(value);
|
||||||
|
final config = context.read<ConfigProvider>();
|
||||||
|
config.prefs.setInt('int_last_publisher_id', value.id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
buttonStyleData: const ButtonStyleData(
|
buttonStyleData: const ButtonStyleData(
|
||||||
@ -474,7 +502,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
onPressed: (_writeController.isBusy || _writeController.publisher == null)
|
onPressed: (_writeController.isBusy || _writeController.publisher == null)
|
||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
_writeController.post(context).then((_) {
|
_writeController.sendPost(context).then((_) {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
Navigator.pop(context, true);
|
Navigator.pop(context, true);
|
||||||
});
|
});
|
||||||
|
@ -13,7 +13,10 @@ import 'package:surface/widgets/post/post_tags_field.dart';
|
|||||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
|
|
||||||
class PostSearchScreen extends StatefulWidget {
|
class PostSearchScreen extends StatefulWidget {
|
||||||
const PostSearchScreen({super.key});
|
final Iterable<String>? initialTags;
|
||||||
|
final Iterable<String>? initialCategories;
|
||||||
|
|
||||||
|
const PostSearchScreen({super.key, this.initialTags, this.initialCategories});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<PostSearchScreen> createState() => _PostSearchScreenState();
|
State<PostSearchScreen> createState() => _PostSearchScreenState();
|
||||||
@ -23,6 +26,7 @@ class _PostSearchScreenState extends State<PostSearchScreen> {
|
|||||||
bool _isBusy = false;
|
bool _isBusy = false;
|
||||||
|
|
||||||
List<String> _searchTags = List.empty(growable: true);
|
List<String> _searchTags = List.empty(growable: true);
|
||||||
|
List<String> _searchCategories = List.empty(growable: true);
|
||||||
|
|
||||||
final List<SnPost> _posts = List.empty(growable: true);
|
final List<SnPost> _posts = List.empty(growable: true);
|
||||||
int? _postCount;
|
int? _postCount;
|
||||||
@ -30,8 +34,18 @@ class _PostSearchScreenState extends State<PostSearchScreen> {
|
|||||||
String _searchTerm = '';
|
String _searchTerm = '';
|
||||||
Duration? _lastTook;
|
Duration? _lastTook;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_searchTags.addAll(widget.initialTags ?? []);
|
||||||
|
_searchCategories.addAll(widget.initialCategories ?? []);
|
||||||
|
if (_searchTags.isNotEmpty || _searchCategories.isNotEmpty) {
|
||||||
|
_fetchPosts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _fetchPosts() async {
|
Future<void> _fetchPosts() async {
|
||||||
if (_searchTerm.isEmpty && _searchTags.isEmpty) return;
|
if (_searchTerm.isEmpty && _searchCategories.isEmpty && _searchTags.isEmpty) return;
|
||||||
if (_postCount != null && _posts.length >= _postCount!) return;
|
if (_postCount != null && _posts.length >= _postCount!) return;
|
||||||
|
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
@ -45,6 +59,7 @@ class _PostSearchScreenState extends State<PostSearchScreen> {
|
|||||||
take: 10,
|
take: 10,
|
||||||
offset: _posts.length,
|
offset: _posts.length,
|
||||||
tags: _searchTags,
|
tags: _searchTags,
|
||||||
|
categories: _searchCategories,
|
||||||
);
|
);
|
||||||
final List<SnPost> out = result.$1;
|
final List<SnPost> out = result.$1;
|
||||||
_postCount = result.$2;
|
_postCount = result.$2;
|
||||||
@ -73,9 +88,25 @@ class _PostSearchScreenState extends State<PostSearchScreen> {
|
|||||||
setState(() => _searchTags = value);
|
setState(() => _searchTags = value);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
const Gap(4),
|
||||||
|
PostCategoriesField(
|
||||||
|
labelText: 'fieldPostCategories'.tr(),
|
||||||
|
initialCategories: _searchCategories,
|
||||||
|
onUpdate: (value) {
|
||||||
|
setState(() => _searchCategories = value);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 24, vertical: 16),
|
).padding(horizontal: 24, vertical: 16),
|
||||||
);
|
).then((_) {
|
||||||
|
_refreshPosts();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _refreshPosts() {
|
||||||
|
_postCount = null;
|
||||||
|
_posts.clear();
|
||||||
|
return _fetchPosts();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -118,8 +149,7 @@ class _PostSearchScreenState extends State<PostSearchScreen> {
|
|||||||
setState(() => _posts[idx] = data);
|
setState(() => _posts[idx] = data);
|
||||||
},
|
},
|
||||||
onDeleted: () {
|
onDeleted: () {
|
||||||
_posts.clear();
|
_refreshPosts();
|
||||||
_fetchPosts();
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -150,10 +180,8 @@ class _PostSearchScreenState extends State<PostSearchScreen> {
|
|||||||
_searchTerm = value;
|
_searchTerm = value;
|
||||||
},
|
},
|
||||||
onSubmitted: (value) {
|
onSubmitted: (value) {
|
||||||
setState(() => _posts.clear());
|
|
||||||
|
|
||||||
_searchTerm = value;
|
_searchTerm = value;
|
||||||
_fetchPosts();
|
_refreshPosts();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (_lastTook != null)
|
if (_lastTook != null)
|
||||||
|
@ -45,17 +45,9 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi
|
|||||||
Future<void> _fetchPublisher() async {
|
Future<void> _fetchPublisher() async {
|
||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final ud = context.read<UserDirectoryProvider>();
|
|
||||||
final rel = context.read<SnRelationshipProvider>();
|
|
||||||
final resp = await sn.client.get('/cgi/co/publishers/${widget.name}');
|
final resp = await sn.client.get('/cgi/co/publishers/${widget.name}');
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
_publisher = SnPublisher.fromJson(resp.data);
|
_publisher = SnPublisher.fromJson(resp.data);
|
||||||
_account = await ud.getAccount(_publisher?.accountId);
|
|
||||||
_accountRelationship = await rel.getRelationship(_account!.id);
|
|
||||||
if (_publisher?.realmId != null && _publisher!.realmId != 0) {
|
|
||||||
final resp = await sn.client.get('/cgi/id/realms/${_publisher!.realmId}');
|
|
||||||
_realm = SnRealm.fromJson(resp.data);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err).then((_) {
|
context.showErrorDialog(err).then((_) {
|
||||||
@ -65,6 +57,20 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi
|
|||||||
} finally {
|
} finally {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final ud = context.read<UserDirectoryProvider>();
|
||||||
|
final rel = context.read<SnRelationshipProvider>();
|
||||||
|
_account = await ud.getAccount(_publisher?.accountId);
|
||||||
|
_accountRelationship = await rel.getRelationship(_account!.id);
|
||||||
|
if (_publisher?.realmId != null && _publisher!.realmId != 0) {
|
||||||
|
final resp = await sn.client.get('/cgi/id/realms/${_publisher!.realmId}');
|
||||||
|
_realm = SnRealm.fromJson(resp.data);
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isSubscribing = false;
|
bool _isSubscribing = false;
|
||||||
@ -277,7 +283,13 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi
|
|||||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
sliver: MultiSliver(
|
sliver: MultiSliver(
|
||||||
children: [
|
children: [
|
||||||
SliverAppBar(
|
Theme(
|
||||||
|
data: Theme.of(context).copyWith(
|
||||||
|
appBarTheme: Theme.of(context).appBarTheme.copyWith(
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: SliverAppBar(
|
||||||
expandedHeight: _appBarHeight,
|
expandedHeight: _appBarHeight,
|
||||||
title: _publisher == null
|
title: _publisher == null
|
||||||
? Text('loading').tr()
|
? Text('loading').tr()
|
||||||
@ -287,7 +299,7 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
text: _publisher!.nick,
|
text: _publisher!.nick,
|
||||||
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
style: Theme.of(context).textTheme.titleLarge!.copyWith(
|
||||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
color: Colors.white,
|
||||||
shadows: labelShadows,
|
shadows: labelShadows,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -342,6 +354,7 @@ class _PostPublisherScreenState extends State<PostPublisherScreen> with SingleTi
|
|||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
if (_publisher != null)
|
if (_publisher != null)
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Container(
|
child: Container(
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
@ -12,11 +13,23 @@ import 'package:path_provider/path_provider.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/providers/config.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/providers/theme.dart';
|
import 'package:surface/providers/theme.dart';
|
||||||
import 'package:surface/theme.dart';
|
import 'package:surface/theme.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
|
||||||
|
const Map<String, Color> kColorSchemes = {
|
||||||
|
'colorSchemeIndigo': Colors.indigo,
|
||||||
|
'colorSchemeBlue': Colors.blue,
|
||||||
|
'colorSchemeGreen': Colors.green,
|
||||||
|
'colorSchemeYellow': Colors.yellow,
|
||||||
|
'colorSchemeOrange': Colors.orange,
|
||||||
|
'colorSchemeRed': Colors.red,
|
||||||
|
'colorSchemeWhite': Colors.white,
|
||||||
|
'colorSchemeBlack': Colors.black,
|
||||||
|
};
|
||||||
|
|
||||||
class SettingsScreen extends StatefulWidget {
|
class SettingsScreen extends StatefulWidget {
|
||||||
const SettingsScreen({super.key});
|
const SettingsScreen({super.key});
|
||||||
|
|
||||||
@ -25,7 +38,7 @@ class SettingsScreen extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _SettingsScreenState extends State<SettingsScreen> {
|
class _SettingsScreenState extends State<SettingsScreen> {
|
||||||
SharedPreferences? _prefs;
|
late final SharedPreferences _prefs;
|
||||||
String _docBasepath = '/';
|
String _docBasepath = '/';
|
||||||
|
|
||||||
final TextEditingController _serverUrlController = TextEditingController();
|
final TextEditingController _serverUrlController = TextEditingController();
|
||||||
@ -39,12 +52,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
SharedPreferences.getInstance().then((prefs) {
|
final config = context.read<ConfigProvider>();
|
||||||
setState(() {
|
_prefs = config.prefs;
|
||||||
_prefs = prefs;
|
_serverUrlController.text = config.serverUrl;
|
||||||
_serverUrlController.text = prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -60,6 +70,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
|
spacing: 16,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
@ -78,7 +89,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
if (image == null) return;
|
if (image == null) return;
|
||||||
|
|
||||||
await File(image.path).copy('$_docBasepath/app_background_image');
|
await File(image.path).copy('$_docBasepath/app_background_image');
|
||||||
_prefs?.setBool('has_background_image', true);
|
_prefs.setBool(kAppBackgroundStoreKey, true);
|
||||||
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
@ -99,29 +110,136 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
File('$_docBasepath/app_background_image').deleteSync();
|
File('$_docBasepath/app_background_image').deleteSync();
|
||||||
_prefs?.remove('has_background_image');
|
_prefs.remove(kAppBackgroundStoreKey);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
if (_prefs != null)
|
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
title: Text('settingsThemeMaterial3').tr(),
|
title: Text('settingsThemeMaterial3').tr(),
|
||||||
subtitle: Text('settingsThemeMaterial3Description').tr(),
|
subtitle: Text('settingsThemeMaterial3Description').tr(),
|
||||||
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
secondary: const Icon(Symbols.new_releases),
|
secondary: const Icon(Symbols.new_releases),
|
||||||
value: _prefs!.getBool(kMaterialYouToggleStoreKey) ?? false,
|
value: _prefs.getBool(kMaterialYouToggleStoreKey) ?? false,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_prefs!.setBool(
|
_prefs.setBool(
|
||||||
kMaterialYouToggleStoreKey,
|
kMaterialYouToggleStoreKey,
|
||||||
value ?? false,
|
value ?? false,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
final th = context.watch<ThemeProvider>();
|
final th = context.read<ThemeProvider>();
|
||||||
th.reloadTheme(useMaterial3: value ?? false);
|
th.reloadTheme(useMaterial3: value ?? false);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.format_paint),
|
||||||
|
title: Text('settingsColorScheme').tr(),
|
||||||
|
subtitle: Text('settingsColorSchemeDescription').tr(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () async {
|
||||||
|
Color pickerColor = Color(_prefs.getInt(kAppColorSchemeStoreKey) ?? Colors.indigo.value);
|
||||||
|
final color = await showDialog<Color?>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: ColorPicker(
|
||||||
|
pickerColor: pickerColor,
|
||||||
|
onColorChanged: (color) => pickerColor = color,
|
||||||
|
enableAlpha: false,
|
||||||
|
hexInputBar: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
child: const Text('dialogDismiss').tr(),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
child: const Text('dialogConfirm').tr(),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(pickerColor);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (color == null || !context.mounted) return;
|
||||||
|
|
||||||
|
_prefs.setInt(kAppColorSchemeStoreKey, color.value);
|
||||||
|
final th = context.read<ThemeProvider>();
|
||||||
|
th.reloadTheme(seedColorOverride: color);
|
||||||
|
setState(() {});
|
||||||
|
|
||||||
|
context.showSnackbar('colorSchemeApplied'.tr());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.palette),
|
||||||
|
title: Text('settingsColorSeed').tr(),
|
||||||
|
subtitle: Text('settingsColorSeedDescription').tr(),
|
||||||
|
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
|
trailing: DropdownButtonHideUnderline(
|
||||||
|
child: DropdownButton2<int?>(
|
||||||
|
isExpanded: true,
|
||||||
|
items: [
|
||||||
|
...kColorSchemes.entries.mapIndexed((idx, ele) {
|
||||||
|
return DropdownMenuItem<int>(
|
||||||
|
value: idx,
|
||||||
|
child: Text(ele.key).tr(),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
DropdownMenuItem<int>(
|
||||||
|
value: -1,
|
||||||
|
child: Text('custom').tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
value: _prefs.getInt(kAppColorSchemeStoreKey) == null
|
||||||
|
? 1
|
||||||
|
: kColorSchemes.values
|
||||||
|
.toList()
|
||||||
|
.indexWhere((ele) => ele.value == _prefs.getInt(kAppColorSchemeStoreKey)),
|
||||||
|
onChanged: (int? value) {
|
||||||
|
if (value != null && value != -1) {
|
||||||
|
_prefs.setInt(kAppColorSchemeStoreKey, kColorSchemes.values.elementAt(value).value);
|
||||||
|
final th = context.read<ThemeProvider>();
|
||||||
|
th.reloadTheme(seedColorOverride: kColorSchemes.values.elementAt(value));
|
||||||
|
setState(() {});
|
||||||
|
|
||||||
|
context.showSnackbar('colorSchemeApplied'.tr());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buttonStyleData: const ButtonStyleData(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 5,
|
||||||
|
),
|
||||||
|
height: 40,
|
||||||
|
width: 160,
|
||||||
|
),
|
||||||
|
menuItemStyleData: const MenuItemStyleData(
|
||||||
|
height: 40,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CheckboxListTile(
|
||||||
|
secondary: const Icon(Symbols.blur_on),
|
||||||
|
title: Text('settingsAppBarTransparent').tr(),
|
||||||
|
subtitle: Text('settingsAppBarTransparentDescription').tr(),
|
||||||
|
contentPadding: const EdgeInsets.only(left: 24, right: 17),
|
||||||
|
value: _prefs.getBool(kAppbarTransparentStoreKey) ?? false,
|
||||||
|
onChanged: (value) {
|
||||||
|
_prefs.setBool(kAppbarTransparentStoreKey, value ?? false);
|
||||||
|
final th = context.read<ThemeProvider>();
|
||||||
|
th.reloadTheme();
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Column(
|
Column(
|
||||||
@ -139,7 +257,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
icon: const Icon(Symbols.save),
|
icon: const Icon(Symbols.save),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
sn.setBaseUrl(_serverUrlController.text);
|
sn.setBaseUrl(_serverUrlController.text);
|
||||||
_prefs?.setString(
|
_prefs.setString(
|
||||||
kNetworkServerStoreKey,
|
kNetworkServerStoreKey,
|
||||||
_serverUrlController.text,
|
_serverUrlController.text,
|
||||||
);
|
);
|
||||||
@ -182,7 +300,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
onChanged: (String? value) {
|
onChanged: (String? value) {
|
||||||
if (value == null) return;
|
if (value == null) return;
|
||||||
_serverUrlController.text = value;
|
_serverUrlController.text = value;
|
||||||
_prefs?.setString(kNetworkServerStoreKey, value);
|
_prefs.setString(kNetworkServerStoreKey, value);
|
||||||
context.showSnackbar('settingsNetworkServerSaved'.tr());
|
context.showSnackbar('settingsNetworkServerSaved'.tr());
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
@ -191,7 +309,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
horizontal: 16,
|
horizontal: 16,
|
||||||
vertical: 5,
|
vertical: 5,
|
||||||
),
|
),
|
||||||
height: 40,
|
height: 56,
|
||||||
width: 160,
|
width: 160,
|
||||||
),
|
),
|
||||||
menuItemStyleData: const MenuItemStyleData(
|
menuItemStyleData: const MenuItemStyleData(
|
||||||
@ -208,13 +326,56 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_serverUrlController.text = kNetworkServerDefault;
|
_serverUrlController.text = kNetworkServerDefault;
|
||||||
_prefs?.remove(kNetworkServerStoreKey);
|
_prefs.remove(kNetworkServerStoreKey);
|
||||||
context.showSnackbar('settingsNetworkServerSaved'.tr());
|
context.showSnackbar('settingsNetworkServerSaved'.tr());
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('settingsPerformance').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4),
|
||||||
|
ListTile(
|
||||||
|
title: Text('settingsImageQuality').tr(),
|
||||||
|
subtitle: Text('settingsImageQualityDescription').tr(),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
leading: const Icon(Symbols.image),
|
||||||
|
trailing: DropdownButtonHideUnderline(
|
||||||
|
child: DropdownButton2<FilterQuality>(
|
||||||
|
value: kImageQualityLevel.values.elementAtOrNull(_prefs.getInt('app_image_quality') ?? 3) ??
|
||||||
|
FilterQuality.high,
|
||||||
|
isExpanded: true,
|
||||||
|
items: kImageQualityLevel.entries
|
||||||
|
.map(
|
||||||
|
(item) => DropdownMenuItem<FilterQuality>(
|
||||||
|
value: item.value,
|
||||||
|
child: Text(item.key).tr().fontSize(14),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
onChanged: (FilterQuality? value) {
|
||||||
|
if (value == null) return;
|
||||||
|
_prefs.setInt('app_image_quality', kImageQualityLevel.values.toList().indexOf(value));
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
buttonStyleData: const ButtonStyleData(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 5,
|
||||||
|
),
|
||||||
|
height: 40,
|
||||||
|
width: 160,
|
||||||
|
),
|
||||||
|
menuItemStyleData: const MenuItemStyleData(
|
||||||
|
height: 60,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -231,7 +392,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
].expand((ele) => [ele, const Gap(16)]).toList(),
|
],
|
||||||
).padding(vertical: 20),
|
).padding(vertical: 20),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
122
lib/screens/sharing.dart
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:developer';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:cross_file/cross_file.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||||||
|
import 'package:surface/controllers/post_write_controller.dart';
|
||||||
|
import 'package:surface/screens/post/post_editor.dart';
|
||||||
|
|
||||||
|
class AppSharingListener extends StatefulWidget {
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const AppSharingListener({super.key, required this.child});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AppSharingListener> createState() => _AppSharingListenerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AppSharingListenerState extends State<AppSharingListener> {
|
||||||
|
late StreamSubscription _shareIntentSubscription;
|
||||||
|
|
||||||
|
void _gotoPost(Iterable<SharedMediaFile> value) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: Text('shareIntent').tr(),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('shareIntentDescription').tr(),
|
||||||
|
const Gap(8),
|
||||||
|
Card(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
|
leading: Icon(Icons.post_add),
|
||||||
|
trailing: const Icon(Icons.chevron_right),
|
||||||
|
title: Text('shareIntentPostStory').tr(),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed(
|
||||||
|
'postEditor',
|
||||||
|
pathParameters: {
|
||||||
|
'mode': 'stories',
|
||||||
|
},
|
||||||
|
extra: PostEditorExtraProps(
|
||||||
|
text: value
|
||||||
|
.where((e) => [SharedMediaType.text, SharedMediaType.url].contains(e.type))
|
||||||
|
.map((e) => e.path).join('\n'),
|
||||||
|
attachments: value
|
||||||
|
.where((e) => [SharedMediaType.video, SharedMediaType.file, SharedMediaType.image].contains(e.type))
|
||||||
|
.map((e) => PostWriteMedia.fromFile(XFile(e.path))).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: Text('dialogDismiss').tr(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _initialize() async {
|
||||||
|
_shareIntentSubscription = ReceiveSharingIntent.instance.getMediaStream().listen((value) {
|
||||||
|
if (value.isEmpty) return;
|
||||||
|
if (mounted) {
|
||||||
|
_gotoPost(value);
|
||||||
|
}
|
||||||
|
}, onError: (err) {
|
||||||
|
log("[ShareIntent] Unable to subscribe: $err");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _initialHandle() {
|
||||||
|
ReceiveSharingIntent.instance.getInitialMedia().then((value) {
|
||||||
|
if (value.isEmpty) return;
|
||||||
|
if (mounted) {
|
||||||
|
_gotoPost(value);
|
||||||
|
}
|
||||||
|
ReceiveSharingIntent.instance.reset();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if(!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
||||||
|
_initialize();
|
||||||
|
_initialHandle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_shareIntentSubscription.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return widget.child;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:surface/providers/config.dart';
|
||||||
|
|
||||||
const kMaterialYouToggleStoreKey = 'app_theme_material_you';
|
const kMaterialYouToggleStoreKey = 'app_theme_material_you';
|
||||||
|
|
||||||
@ -10,7 +11,7 @@ class ThemeSet {
|
|||||||
ThemeSet({required this.light, required this.dark});
|
ThemeSet({required this.light, required this.dark});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ThemeSet> createAppThemeSet({bool? useMaterial3}) async {
|
Future<ThemeSet> createAppThemeSet({Color? seedColorOverride, bool? useMaterial3}) async {
|
||||||
return ThemeSet(
|
return ThemeSet(
|
||||||
light: await createAppTheme(Brightness.light, useMaterial3: useMaterial3),
|
light: await createAppTheme(Brightness.light, useMaterial3: useMaterial3),
|
||||||
dark: await createAppTheme(Brightness.dark, useMaterial3: useMaterial3),
|
dark: await createAppTheme(Brightness.dark, useMaterial3: useMaterial3),
|
||||||
@ -19,16 +20,21 @@ Future<ThemeSet> createAppThemeSet({bool? useMaterial3}) async {
|
|||||||
|
|
||||||
Future<ThemeData> createAppTheme(
|
Future<ThemeData> createAppTheme(
|
||||||
Brightness brightness, {
|
Brightness brightness, {
|
||||||
|
Color? seedColorOverride,
|
||||||
bool? useMaterial3,
|
bool? useMaterial3,
|
||||||
}) async {
|
}) async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
|
final seedColorString = prefs.getInt(kAppColorSchemeStoreKey);
|
||||||
|
final seedColor = seedColorString != null ? Color(seedColorString) : Colors.indigo;
|
||||||
|
|
||||||
final colorScheme = ColorScheme.fromSeed(
|
final colorScheme = ColorScheme.fromSeed(
|
||||||
seedColor: Colors.indigo,
|
seedColor: seedColorOverride ?? seedColor,
|
||||||
brightness: brightness,
|
brightness: brightness,
|
||||||
);
|
);
|
||||||
|
|
||||||
final hasBackground = prefs.getBool('has_background_image') ?? false;
|
final hasBackground = prefs.getBool(kAppBackgroundStoreKey) ?? false;
|
||||||
|
final hasAppBarBlurry = prefs.getBool(kAppbarTransparentStoreKey) ?? false;
|
||||||
|
|
||||||
return ThemeData(
|
return ThemeData(
|
||||||
useMaterial3: useMaterial3 ?? (prefs.getBool(kMaterialYouToggleStoreKey) ?? false),
|
useMaterial3: useMaterial3 ?? (prefs.getBool(kMaterialYouToggleStoreKey) ?? false),
|
||||||
@ -42,8 +48,9 @@ Future<ThemeData> createAppTheme(
|
|||||||
),
|
),
|
||||||
appBarTheme: AppBarTheme(
|
appBarTheme: AppBarTheme(
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
backgroundColor: hasBackground ? colorScheme.primary.withOpacity(0.75) : colorScheme.primary,
|
elevation: hasAppBarBlurry ? 0 : null,
|
||||||
foregroundColor: colorScheme.onPrimary,
|
backgroundColor: hasAppBarBlurry ? colorScheme.surfaceContainer.withAlpha(200) : colorScheme.primary,
|
||||||
|
foregroundColor: hasAppBarBlurry ? colorScheme.onSurface : colorScheme.onPrimary,
|
||||||
),
|
),
|
||||||
scaffoldBackgroundColor: Colors.transparent,
|
scaffoldBackgroundColor: Colors.transparent,
|
||||||
);
|
);
|
||||||
|
@ -3,6 +3,8 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
|||||||
part 'check_in.freezed.dart';
|
part 'check_in.freezed.dart';
|
||||||
part 'check_in.g.dart';
|
part 'check_in.g.dart';
|
||||||
|
|
||||||
|
const List<String> kCheckInResultTierSymbols = ['大凶', '凶', '中平', '吉', '大吉'];
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class SnCheckInRecord with _$SnCheckInRecord {
|
class SnCheckInRecord with _$SnCheckInRecord {
|
||||||
const SnCheckInRecord._();
|
const SnCheckInRecord._();
|
||||||
@ -21,11 +23,5 @@ class SnCheckInRecord with _$SnCheckInRecord {
|
|||||||
factory SnCheckInRecord.fromJson(Map<String, dynamic> json) =>
|
factory SnCheckInRecord.fromJson(Map<String, dynamic> json) =>
|
||||||
_$SnCheckInRecordFromJson(json);
|
_$SnCheckInRecordFromJson(json);
|
||||||
|
|
||||||
String get symbol => switch (resultTier) {
|
String get symbol => kCheckInResultTierSymbols[resultTier];
|
||||||
0 => '大凶',
|
|
||||||
1 => '凶',
|
|
||||||
2 => '中平',
|
|
||||||
3 => '吉',
|
|
||||||
_ => '大吉',
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
28
lib/types/link.dart
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'link.g.dart';
|
||||||
|
part 'link.freezed.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SnLinkMeta with _$SnLinkMeta {
|
||||||
|
const SnLinkMeta._();
|
||||||
|
|
||||||
|
const factory SnLinkMeta({
|
||||||
|
required int id,
|
||||||
|
required DateTime createdAt,
|
||||||
|
required DateTime updatedAt,
|
||||||
|
required DateTime? deletedAt,
|
||||||
|
required String entryId,
|
||||||
|
required String? icon,
|
||||||
|
required String url,
|
||||||
|
required String? title,
|
||||||
|
required String? image,
|
||||||
|
required String? video,
|
||||||
|
required String? audio,
|
||||||
|
required String? description,
|
||||||
|
required String? siteName,
|
||||||
|
required String? type,
|
||||||
|
}) = _SnLinkMeta;
|
||||||
|
|
||||||
|
factory SnLinkMeta.fromJson(Map<String, dynamic> json) => _$SnLinkMetaFromJson(json);
|
||||||
|
}
|
450
lib/types/link.freezed.dart
Normal file
@ -0,0 +1,450 @@
|
|||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'link.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
final _privateConstructorUsedError = UnsupportedError(
|
||||||
|
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||||
|
|
||||||
|
SnLinkMeta _$SnLinkMetaFromJson(Map<String, dynamic> json) {
|
||||||
|
return _SnLinkMeta.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnLinkMeta {
|
||||||
|
int get id => throw _privateConstructorUsedError;
|
||||||
|
DateTime get createdAt => throw _privateConstructorUsedError;
|
||||||
|
DateTime get updatedAt => throw _privateConstructorUsedError;
|
||||||
|
DateTime? get deletedAt => throw _privateConstructorUsedError;
|
||||||
|
String get entryId => throw _privateConstructorUsedError;
|
||||||
|
String? get icon => throw _privateConstructorUsedError;
|
||||||
|
String get url => throw _privateConstructorUsedError;
|
||||||
|
String? get title => throw _privateConstructorUsedError;
|
||||||
|
String? get image => throw _privateConstructorUsedError;
|
||||||
|
String? get video => throw _privateConstructorUsedError;
|
||||||
|
String? get audio => throw _privateConstructorUsedError;
|
||||||
|
String? get description => throw _privateConstructorUsedError;
|
||||||
|
String? get siteName => throw _privateConstructorUsedError;
|
||||||
|
String? get type => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Serializes this SnLinkMeta to a JSON map.
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of SnLinkMeta
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$SnLinkMetaCopyWith<SnLinkMeta> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $SnLinkMetaCopyWith<$Res> {
|
||||||
|
factory $SnLinkMetaCopyWith(
|
||||||
|
SnLinkMeta value, $Res Function(SnLinkMeta) then) =
|
||||||
|
_$SnLinkMetaCopyWithImpl<$Res, SnLinkMeta>;
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{int id,
|
||||||
|
DateTime createdAt,
|
||||||
|
DateTime updatedAt,
|
||||||
|
DateTime? deletedAt,
|
||||||
|
String entryId,
|
||||||
|
String? icon,
|
||||||
|
String url,
|
||||||
|
String? title,
|
||||||
|
String? image,
|
||||||
|
String? video,
|
||||||
|
String? audio,
|
||||||
|
String? description,
|
||||||
|
String? siteName,
|
||||||
|
String? type});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnLinkMetaCopyWithImpl<$Res, $Val extends SnLinkMeta>
|
||||||
|
implements $SnLinkMetaCopyWith<$Res> {
|
||||||
|
_$SnLinkMetaCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnLinkMeta
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? createdAt = null,
|
||||||
|
Object? updatedAt = null,
|
||||||
|
Object? deletedAt = freezed,
|
||||||
|
Object? entryId = null,
|
||||||
|
Object? icon = freezed,
|
||||||
|
Object? url = null,
|
||||||
|
Object? title = freezed,
|
||||||
|
Object? image = freezed,
|
||||||
|
Object? video = freezed,
|
||||||
|
Object? audio = freezed,
|
||||||
|
Object? description = freezed,
|
||||||
|
Object? siteName = freezed,
|
||||||
|
Object? type = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
createdAt: null == createdAt
|
||||||
|
? _value.createdAt
|
||||||
|
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
updatedAt: null == updatedAt
|
||||||
|
? _value.updatedAt
|
||||||
|
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
deletedAt: freezed == deletedAt
|
||||||
|
? _value.deletedAt
|
||||||
|
: deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
entryId: null == entryId
|
||||||
|
? _value.entryId
|
||||||
|
: entryId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
icon: freezed == icon
|
||||||
|
? _value.icon
|
||||||
|
: icon // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
url: null == url
|
||||||
|
? _value.url
|
||||||
|
: url // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
title: freezed == title
|
||||||
|
? _value.title
|
||||||
|
: title // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
image: freezed == image
|
||||||
|
? _value.image
|
||||||
|
: image // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
video: freezed == video
|
||||||
|
? _value.video
|
||||||
|
: video // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
audio: freezed == audio
|
||||||
|
? _value.audio
|
||||||
|
: audio // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
description: freezed == description
|
||||||
|
? _value.description
|
||||||
|
: description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
siteName: freezed == siteName
|
||||||
|
? _value.siteName
|
||||||
|
: siteName // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
type: freezed == type
|
||||||
|
? _value.type
|
||||||
|
: type // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$SnLinkMetaImplCopyWith<$Res>
|
||||||
|
implements $SnLinkMetaCopyWith<$Res> {
|
||||||
|
factory _$$SnLinkMetaImplCopyWith(
|
||||||
|
_$SnLinkMetaImpl value, $Res Function(_$SnLinkMetaImpl) then) =
|
||||||
|
__$$SnLinkMetaImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{int id,
|
||||||
|
DateTime createdAt,
|
||||||
|
DateTime updatedAt,
|
||||||
|
DateTime? deletedAt,
|
||||||
|
String entryId,
|
||||||
|
String? icon,
|
||||||
|
String url,
|
||||||
|
String? title,
|
||||||
|
String? image,
|
||||||
|
String? video,
|
||||||
|
String? audio,
|
||||||
|
String? description,
|
||||||
|
String? siteName,
|
||||||
|
String? type});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$SnLinkMetaImplCopyWithImpl<$Res>
|
||||||
|
extends _$SnLinkMetaCopyWithImpl<$Res, _$SnLinkMetaImpl>
|
||||||
|
implements _$$SnLinkMetaImplCopyWith<$Res> {
|
||||||
|
__$$SnLinkMetaImplCopyWithImpl(
|
||||||
|
_$SnLinkMetaImpl _value, $Res Function(_$SnLinkMetaImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of SnLinkMeta
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? createdAt = null,
|
||||||
|
Object? updatedAt = null,
|
||||||
|
Object? deletedAt = freezed,
|
||||||
|
Object? entryId = null,
|
||||||
|
Object? icon = freezed,
|
||||||
|
Object? url = null,
|
||||||
|
Object? title = freezed,
|
||||||
|
Object? image = freezed,
|
||||||
|
Object? video = freezed,
|
||||||
|
Object? audio = freezed,
|
||||||
|
Object? description = freezed,
|
||||||
|
Object? siteName = freezed,
|
||||||
|
Object? type = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_$SnLinkMetaImpl(
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
createdAt: null == createdAt
|
||||||
|
? _value.createdAt
|
||||||
|
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
updatedAt: null == updatedAt
|
||||||
|
? _value.updatedAt
|
||||||
|
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
deletedAt: freezed == deletedAt
|
||||||
|
? _value.deletedAt
|
||||||
|
: deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
entryId: null == entryId
|
||||||
|
? _value.entryId
|
||||||
|
: entryId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
icon: freezed == icon
|
||||||
|
? _value.icon
|
||||||
|
: icon // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
url: null == url
|
||||||
|
? _value.url
|
||||||
|
: url // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
title: freezed == title
|
||||||
|
? _value.title
|
||||||
|
: title // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
image: freezed == image
|
||||||
|
? _value.image
|
||||||
|
: image // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
video: freezed == video
|
||||||
|
? _value.video
|
||||||
|
: video // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
audio: freezed == audio
|
||||||
|
? _value.audio
|
||||||
|
: audio // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
description: freezed == description
|
||||||
|
? _value.description
|
||||||
|
: description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
siteName: freezed == siteName
|
||||||
|
? _value.siteName
|
||||||
|
: siteName // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
type: freezed == type
|
||||||
|
? _value.type
|
||||||
|
: type // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$SnLinkMetaImpl extends _SnLinkMeta {
|
||||||
|
const _$SnLinkMetaImpl(
|
||||||
|
{required this.id,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
|
required this.deletedAt,
|
||||||
|
required this.entryId,
|
||||||
|
required this.icon,
|
||||||
|
required this.url,
|
||||||
|
required this.title,
|
||||||
|
required this.image,
|
||||||
|
required this.video,
|
||||||
|
required this.audio,
|
||||||
|
required this.description,
|
||||||
|
required this.siteName,
|
||||||
|
required this.type})
|
||||||
|
: super._();
|
||||||
|
|
||||||
|
factory _$SnLinkMetaImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$SnLinkMetaImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final int id;
|
||||||
|
@override
|
||||||
|
final DateTime createdAt;
|
||||||
|
@override
|
||||||
|
final DateTime updatedAt;
|
||||||
|
@override
|
||||||
|
final DateTime? deletedAt;
|
||||||
|
@override
|
||||||
|
final String entryId;
|
||||||
|
@override
|
||||||
|
final String? icon;
|
||||||
|
@override
|
||||||
|
final String url;
|
||||||
|
@override
|
||||||
|
final String? title;
|
||||||
|
@override
|
||||||
|
final String? image;
|
||||||
|
@override
|
||||||
|
final String? video;
|
||||||
|
@override
|
||||||
|
final String? audio;
|
||||||
|
@override
|
||||||
|
final String? description;
|
||||||
|
@override
|
||||||
|
final String? siteName;
|
||||||
|
@override
|
||||||
|
final String? type;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnLinkMeta(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, entryId: $entryId, icon: $icon, url: $url, title: $title, image: $image, video: $video, audio: $audio, description: $description, siteName: $siteName, type: $type)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$SnLinkMetaImpl &&
|
||||||
|
(identical(other.id, id) || other.id == id) &&
|
||||||
|
(identical(other.createdAt, createdAt) ||
|
||||||
|
other.createdAt == createdAt) &&
|
||||||
|
(identical(other.updatedAt, updatedAt) ||
|
||||||
|
other.updatedAt == updatedAt) &&
|
||||||
|
(identical(other.deletedAt, deletedAt) ||
|
||||||
|
other.deletedAt == deletedAt) &&
|
||||||
|
(identical(other.entryId, entryId) || other.entryId == entryId) &&
|
||||||
|
(identical(other.icon, icon) || other.icon == icon) &&
|
||||||
|
(identical(other.url, url) || other.url == url) &&
|
||||||
|
(identical(other.title, title) || other.title == title) &&
|
||||||
|
(identical(other.image, image) || other.image == image) &&
|
||||||
|
(identical(other.video, video) || other.video == video) &&
|
||||||
|
(identical(other.audio, audio) || other.audio == audio) &&
|
||||||
|
(identical(other.description, description) ||
|
||||||
|
other.description == description) &&
|
||||||
|
(identical(other.siteName, siteName) ||
|
||||||
|
other.siteName == siteName) &&
|
||||||
|
(identical(other.type, type) || other.type == type));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
runtimeType,
|
||||||
|
id,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
deletedAt,
|
||||||
|
entryId,
|
||||||
|
icon,
|
||||||
|
url,
|
||||||
|
title,
|
||||||
|
image,
|
||||||
|
video,
|
||||||
|
audio,
|
||||||
|
description,
|
||||||
|
siteName,
|
||||||
|
type);
|
||||||
|
|
||||||
|
/// Create a copy of SnLinkMeta
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$SnLinkMetaImplCopyWith<_$SnLinkMetaImpl> get copyWith =>
|
||||||
|
__$$SnLinkMetaImplCopyWithImpl<_$SnLinkMetaImpl>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$SnLinkMetaImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _SnLinkMeta extends SnLinkMeta {
|
||||||
|
const factory _SnLinkMeta(
|
||||||
|
{required final int id,
|
||||||
|
required final DateTime createdAt,
|
||||||
|
required final DateTime updatedAt,
|
||||||
|
required final DateTime? deletedAt,
|
||||||
|
required final String entryId,
|
||||||
|
required final String? icon,
|
||||||
|
required final String url,
|
||||||
|
required final String? title,
|
||||||
|
required final String? image,
|
||||||
|
required final String? video,
|
||||||
|
required final String? audio,
|
||||||
|
required final String? description,
|
||||||
|
required final String? siteName,
|
||||||
|
required final String? type}) = _$SnLinkMetaImpl;
|
||||||
|
const _SnLinkMeta._() : super._();
|
||||||
|
|
||||||
|
factory _SnLinkMeta.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$SnLinkMetaImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get id;
|
||||||
|
@override
|
||||||
|
DateTime get createdAt;
|
||||||
|
@override
|
||||||
|
DateTime get updatedAt;
|
||||||
|
@override
|
||||||
|
DateTime? get deletedAt;
|
||||||
|
@override
|
||||||
|
String get entryId;
|
||||||
|
@override
|
||||||
|
String? get icon;
|
||||||
|
@override
|
||||||
|
String get url;
|
||||||
|
@override
|
||||||
|
String? get title;
|
||||||
|
@override
|
||||||
|
String? get image;
|
||||||
|
@override
|
||||||
|
String? get video;
|
||||||
|
@override
|
||||||
|
String? get audio;
|
||||||
|
@override
|
||||||
|
String? get description;
|
||||||
|
@override
|
||||||
|
String? get siteName;
|
||||||
|
@override
|
||||||
|
String? get type;
|
||||||
|
|
||||||
|
/// Create a copy of SnLinkMeta
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$SnLinkMetaImplCopyWith<_$SnLinkMetaImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
45
lib/types/link.g.dart
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'link.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_$SnLinkMetaImpl _$$SnLinkMetaImplFromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnLinkMetaImpl(
|
||||||
|
id: (json['id'] as num).toInt(),
|
||||||
|
createdAt: DateTime.parse(json['created_at'] as String),
|
||||||
|
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||||
|
deletedAt: json['deleted_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['deleted_at'] as String),
|
||||||
|
entryId: json['entry_id'] as String,
|
||||||
|
icon: json['icon'] as String?,
|
||||||
|
url: json['url'] as String,
|
||||||
|
title: json['title'] as String?,
|
||||||
|
image: json['image'] as String?,
|
||||||
|
video: json['video'] as String?,
|
||||||
|
audio: json['audio'] as String?,
|
||||||
|
description: json['description'] as String?,
|
||||||
|
siteName: json['site_name'] as String?,
|
||||||
|
type: json['type'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$SnLinkMetaImplToJson(_$SnLinkMetaImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
|
'entry_id': instance.entryId,
|
||||||
|
'icon': instance.icon,
|
||||||
|
'url': instance.url,
|
||||||
|
'title': instance.title,
|
||||||
|
'image': instance.image,
|
||||||
|
'video': instance.video,
|
||||||
|
'audio': instance.audio,
|
||||||
|
'description': instance.description,
|
||||||
|
'site_name': instance.siteName,
|
||||||
|
'type': instance.type,
|
||||||
|
};
|
@ -19,7 +19,7 @@ class SnPost with _$SnPost {
|
|||||||
required String? alias,
|
required String? alias,
|
||||||
required String? aliasPrefix,
|
required String? aliasPrefix,
|
||||||
@Default([]) List<SnPostTag> tags,
|
@Default([]) List<SnPostTag> tags,
|
||||||
@Default([]) List<dynamic> categories,
|
@Default([]) List<SnPostCategory> categories,
|
||||||
required List<SnPost>? replies,
|
required List<SnPost>? replies,
|
||||||
required int? replyId,
|
required int? replyId,
|
||||||
required int? repostId,
|
required int? repostId,
|
||||||
@ -67,6 +67,23 @@ class SnPostTag with _$SnPostTag {
|
|||||||
_$SnPostTagFromJson(json);
|
_$SnPostTagFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SnPostCategory with _$SnPostCategory {
|
||||||
|
const factory SnPostCategory({
|
||||||
|
required int id,
|
||||||
|
required DateTime createdAt,
|
||||||
|
required DateTime updatedAt,
|
||||||
|
required dynamic deletedAt,
|
||||||
|
required String alias,
|
||||||
|
required String name,
|
||||||
|
required String description,
|
||||||
|
required dynamic posts,
|
||||||
|
}) = _SnPostCategory;
|
||||||
|
|
||||||
|
factory SnPostCategory.fromJson(Map<String, Object?> json) =>
|
||||||
|
_$SnPostCategoryFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class SnPostPreload with _$SnPostPreload {
|
class SnPostPreload with _$SnPostPreload {
|
||||||
const factory SnPostPreload({
|
const factory SnPostPreload({
|
||||||
|
@ -30,7 +30,7 @@ mixin _$SnPost {
|
|||||||
String? get alias => throw _privateConstructorUsedError;
|
String? get alias => throw _privateConstructorUsedError;
|
||||||
String? get aliasPrefix => throw _privateConstructorUsedError;
|
String? get aliasPrefix => throw _privateConstructorUsedError;
|
||||||
List<SnPostTag> get tags => throw _privateConstructorUsedError;
|
List<SnPostTag> get tags => throw _privateConstructorUsedError;
|
||||||
List<dynamic> get categories => throw _privateConstructorUsedError;
|
List<SnPostCategory> get categories => throw _privateConstructorUsedError;
|
||||||
List<SnPost>? get replies => throw _privateConstructorUsedError;
|
List<SnPost>? get replies => throw _privateConstructorUsedError;
|
||||||
int? get replyId => throw _privateConstructorUsedError;
|
int? get replyId => throw _privateConstructorUsedError;
|
||||||
int? get repostId => throw _privateConstructorUsedError;
|
int? get repostId => throw _privateConstructorUsedError;
|
||||||
@ -77,7 +77,7 @@ abstract class $SnPostCopyWith<$Res> {
|
|||||||
String? alias,
|
String? alias,
|
||||||
String? aliasPrefix,
|
String? aliasPrefix,
|
||||||
List<SnPostTag> tags,
|
List<SnPostTag> tags,
|
||||||
List<dynamic> categories,
|
List<SnPostCategory> categories,
|
||||||
List<SnPost>? replies,
|
List<SnPost>? replies,
|
||||||
int? replyId,
|
int? replyId,
|
||||||
int? repostId,
|
int? repostId,
|
||||||
@ -197,7 +197,7 @@ class _$SnPostCopyWithImpl<$Res, $Val extends SnPost>
|
|||||||
categories: null == categories
|
categories: null == categories
|
||||||
? _value.categories
|
? _value.categories
|
||||||
: categories // ignore: cast_nullable_to_non_nullable
|
: categories // ignore: cast_nullable_to_non_nullable
|
||||||
as List<dynamic>,
|
as List<SnPostCategory>,
|
||||||
replies: freezed == replies
|
replies: freezed == replies
|
||||||
? _value.replies
|
? _value.replies
|
||||||
: replies // ignore: cast_nullable_to_non_nullable
|
: replies // ignore: cast_nullable_to_non_nullable
|
||||||
@ -362,7 +362,7 @@ abstract class _$$SnPostImplCopyWith<$Res> implements $SnPostCopyWith<$Res> {
|
|||||||
String? alias,
|
String? alias,
|
||||||
String? aliasPrefix,
|
String? aliasPrefix,
|
||||||
List<SnPostTag> tags,
|
List<SnPostTag> tags,
|
||||||
List<dynamic> categories,
|
List<SnPostCategory> categories,
|
||||||
List<SnPost>? replies,
|
List<SnPost>? replies,
|
||||||
int? replyId,
|
int? replyId,
|
||||||
int? repostId,
|
int? repostId,
|
||||||
@ -485,7 +485,7 @@ class __$$SnPostImplCopyWithImpl<$Res>
|
|||||||
categories: null == categories
|
categories: null == categories
|
||||||
? _value._categories
|
? _value._categories
|
||||||
: categories // ignore: cast_nullable_to_non_nullable
|
: categories // ignore: cast_nullable_to_non_nullable
|
||||||
as List<dynamic>,
|
as List<SnPostCategory>,
|
||||||
replies: freezed == replies
|
replies: freezed == replies
|
||||||
? _value._replies
|
? _value._replies
|
||||||
: replies // ignore: cast_nullable_to_non_nullable
|
: replies // ignore: cast_nullable_to_non_nullable
|
||||||
@ -584,7 +584,7 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
required this.alias,
|
required this.alias,
|
||||||
required this.aliasPrefix,
|
required this.aliasPrefix,
|
||||||
final List<SnPostTag> tags = const [],
|
final List<SnPostTag> tags = const [],
|
||||||
final List<dynamic> categories = const [],
|
final List<SnPostCategory> categories = const [],
|
||||||
required final List<SnPost>? replies,
|
required final List<SnPost>? replies,
|
||||||
required this.replyId,
|
required this.replyId,
|
||||||
required this.repostId,
|
required this.repostId,
|
||||||
@ -649,10 +649,10 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
return EqualUnmodifiableListView(_tags);
|
return EqualUnmodifiableListView(_tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<dynamic> _categories;
|
final List<SnPostCategory> _categories;
|
||||||
@override
|
@override
|
||||||
@JsonKey()
|
@JsonKey()
|
||||||
List<dynamic> get categories {
|
List<SnPostCategory> get categories {
|
||||||
if (_categories is EqualUnmodifiableListView) return _categories;
|
if (_categories is EqualUnmodifiableListView) return _categories;
|
||||||
// ignore: implicit_dynamic_type
|
// ignore: implicit_dynamic_type
|
||||||
return EqualUnmodifiableListView(_categories);
|
return EqualUnmodifiableListView(_categories);
|
||||||
@ -853,7 +853,7 @@ abstract class _SnPost extends SnPost {
|
|||||||
required final String? alias,
|
required final String? alias,
|
||||||
required final String? aliasPrefix,
|
required final String? aliasPrefix,
|
||||||
final List<SnPostTag> tags,
|
final List<SnPostTag> tags,
|
||||||
final List<dynamic> categories,
|
final List<SnPostCategory> categories,
|
||||||
required final List<SnPost>? replies,
|
required final List<SnPost>? replies,
|
||||||
required final int? replyId,
|
required final int? replyId,
|
||||||
required final int? repostId,
|
required final int? repostId,
|
||||||
@ -899,7 +899,7 @@ abstract class _SnPost extends SnPost {
|
|||||||
@override
|
@override
|
||||||
List<SnPostTag> get tags;
|
List<SnPostTag> get tags;
|
||||||
@override
|
@override
|
||||||
List<dynamic> get categories;
|
List<SnPostCategory> get categories;
|
||||||
@override
|
@override
|
||||||
List<SnPost>? get replies;
|
List<SnPost>? get replies;
|
||||||
@override
|
@override
|
||||||
@ -1253,6 +1253,312 @@ abstract class _SnPostTag implements SnPostTag {
|
|||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SnPostCategory _$SnPostCategoryFromJson(Map<String, dynamic> json) {
|
||||||
|
return _SnPostCategory.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnPostCategory {
|
||||||
|
int get id => throw _privateConstructorUsedError;
|
||||||
|
DateTime get createdAt => throw _privateConstructorUsedError;
|
||||||
|
DateTime get updatedAt => throw _privateConstructorUsedError;
|
||||||
|
dynamic get deletedAt => throw _privateConstructorUsedError;
|
||||||
|
String get alias => throw _privateConstructorUsedError;
|
||||||
|
String get name => throw _privateConstructorUsedError;
|
||||||
|
String get description => throw _privateConstructorUsedError;
|
||||||
|
dynamic get posts => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Serializes this SnPostCategory to a JSON map.
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of SnPostCategory
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$SnPostCategoryCopyWith<SnPostCategory> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $SnPostCategoryCopyWith<$Res> {
|
||||||
|
factory $SnPostCategoryCopyWith(
|
||||||
|
SnPostCategory value, $Res Function(SnPostCategory) then) =
|
||||||
|
_$SnPostCategoryCopyWithImpl<$Res, SnPostCategory>;
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{int id,
|
||||||
|
DateTime createdAt,
|
||||||
|
DateTime updatedAt,
|
||||||
|
dynamic deletedAt,
|
||||||
|
String alias,
|
||||||
|
String name,
|
||||||
|
String description,
|
||||||
|
dynamic posts});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnPostCategoryCopyWithImpl<$Res, $Val extends SnPostCategory>
|
||||||
|
implements $SnPostCategoryCopyWith<$Res> {
|
||||||
|
_$SnPostCategoryCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnPostCategory
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? createdAt = null,
|
||||||
|
Object? updatedAt = null,
|
||||||
|
Object? deletedAt = freezed,
|
||||||
|
Object? alias = null,
|
||||||
|
Object? name = null,
|
||||||
|
Object? description = null,
|
||||||
|
Object? posts = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
createdAt: null == createdAt
|
||||||
|
? _value.createdAt
|
||||||
|
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
updatedAt: null == updatedAt
|
||||||
|
? _value.updatedAt
|
||||||
|
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
deletedAt: freezed == deletedAt
|
||||||
|
? _value.deletedAt
|
||||||
|
: deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,
|
||||||
|
alias: null == alias
|
||||||
|
? _value.alias
|
||||||
|
: alias // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
name: null == name
|
||||||
|
? _value.name
|
||||||
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
description: null == description
|
||||||
|
? _value.description
|
||||||
|
: description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
posts: freezed == posts
|
||||||
|
? _value.posts
|
||||||
|
: posts // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$SnPostCategoryImplCopyWith<$Res>
|
||||||
|
implements $SnPostCategoryCopyWith<$Res> {
|
||||||
|
factory _$$SnPostCategoryImplCopyWith(_$SnPostCategoryImpl value,
|
||||||
|
$Res Function(_$SnPostCategoryImpl) then) =
|
||||||
|
__$$SnPostCategoryImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{int id,
|
||||||
|
DateTime createdAt,
|
||||||
|
DateTime updatedAt,
|
||||||
|
dynamic deletedAt,
|
||||||
|
String alias,
|
||||||
|
String name,
|
||||||
|
String description,
|
||||||
|
dynamic posts});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$SnPostCategoryImplCopyWithImpl<$Res>
|
||||||
|
extends _$SnPostCategoryCopyWithImpl<$Res, _$SnPostCategoryImpl>
|
||||||
|
implements _$$SnPostCategoryImplCopyWith<$Res> {
|
||||||
|
__$$SnPostCategoryImplCopyWithImpl(
|
||||||
|
_$SnPostCategoryImpl _value, $Res Function(_$SnPostCategoryImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of SnPostCategory
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? id = null,
|
||||||
|
Object? createdAt = null,
|
||||||
|
Object? updatedAt = null,
|
||||||
|
Object? deletedAt = freezed,
|
||||||
|
Object? alias = null,
|
||||||
|
Object? name = null,
|
||||||
|
Object? description = null,
|
||||||
|
Object? posts = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_$SnPostCategoryImpl(
|
||||||
|
id: null == id
|
||||||
|
? _value.id
|
||||||
|
: id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
createdAt: null == createdAt
|
||||||
|
? _value.createdAt
|
||||||
|
: createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
updatedAt: null == updatedAt
|
||||||
|
? _value.updatedAt
|
||||||
|
: updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
deletedAt: freezed == deletedAt
|
||||||
|
? _value.deletedAt
|
||||||
|
: deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,
|
||||||
|
alias: null == alias
|
||||||
|
? _value.alias
|
||||||
|
: alias // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
name: null == name
|
||||||
|
? _value.name
|
||||||
|
: name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
description: null == description
|
||||||
|
? _value.description
|
||||||
|
: description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
posts: freezed == posts
|
||||||
|
? _value.posts
|
||||||
|
: posts // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$SnPostCategoryImpl implements _SnPostCategory {
|
||||||
|
const _$SnPostCategoryImpl(
|
||||||
|
{required this.id,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.updatedAt,
|
||||||
|
required this.deletedAt,
|
||||||
|
required this.alias,
|
||||||
|
required this.name,
|
||||||
|
required this.description,
|
||||||
|
required this.posts});
|
||||||
|
|
||||||
|
factory _$SnPostCategoryImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$SnPostCategoryImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final int id;
|
||||||
|
@override
|
||||||
|
final DateTime createdAt;
|
||||||
|
@override
|
||||||
|
final DateTime updatedAt;
|
||||||
|
@override
|
||||||
|
final dynamic deletedAt;
|
||||||
|
@override
|
||||||
|
final String alias;
|
||||||
|
@override
|
||||||
|
final String name;
|
||||||
|
@override
|
||||||
|
final String description;
|
||||||
|
@override
|
||||||
|
final dynamic posts;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnPostCategory(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, alias: $alias, name: $name, description: $description, posts: $posts)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$SnPostCategoryImpl &&
|
||||||
|
(identical(other.id, id) || other.id == id) &&
|
||||||
|
(identical(other.createdAt, createdAt) ||
|
||||||
|
other.createdAt == createdAt) &&
|
||||||
|
(identical(other.updatedAt, updatedAt) ||
|
||||||
|
other.updatedAt == updatedAt) &&
|
||||||
|
const DeepCollectionEquality().equals(other.deletedAt, deletedAt) &&
|
||||||
|
(identical(other.alias, alias) || other.alias == alias) &&
|
||||||
|
(identical(other.name, name) || other.name == name) &&
|
||||||
|
(identical(other.description, description) ||
|
||||||
|
other.description == description) &&
|
||||||
|
const DeepCollectionEquality().equals(other.posts, posts));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
runtimeType,
|
||||||
|
id,
|
||||||
|
createdAt,
|
||||||
|
updatedAt,
|
||||||
|
const DeepCollectionEquality().hash(deletedAt),
|
||||||
|
alias,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
const DeepCollectionEquality().hash(posts));
|
||||||
|
|
||||||
|
/// Create a copy of SnPostCategory
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$SnPostCategoryImplCopyWith<_$SnPostCategoryImpl> get copyWith =>
|
||||||
|
__$$SnPostCategoryImplCopyWithImpl<_$SnPostCategoryImpl>(
|
||||||
|
this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$SnPostCategoryImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _SnPostCategory implements SnPostCategory {
|
||||||
|
const factory _SnPostCategory(
|
||||||
|
{required final int id,
|
||||||
|
required final DateTime createdAt,
|
||||||
|
required final DateTime updatedAt,
|
||||||
|
required final dynamic deletedAt,
|
||||||
|
required final String alias,
|
||||||
|
required final String name,
|
||||||
|
required final String description,
|
||||||
|
required final dynamic posts}) = _$SnPostCategoryImpl;
|
||||||
|
|
||||||
|
factory _SnPostCategory.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$SnPostCategoryImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get id;
|
||||||
|
@override
|
||||||
|
DateTime get createdAt;
|
||||||
|
@override
|
||||||
|
DateTime get updatedAt;
|
||||||
|
@override
|
||||||
|
dynamic get deletedAt;
|
||||||
|
@override
|
||||||
|
String get alias;
|
||||||
|
@override
|
||||||
|
String get name;
|
||||||
|
@override
|
||||||
|
String get description;
|
||||||
|
@override
|
||||||
|
dynamic get posts;
|
||||||
|
|
||||||
|
/// Create a copy of SnPostCategory
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$SnPostCategoryImplCopyWith<_$SnPostCategoryImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
SnPostPreload _$SnPostPreloadFromJson(Map<String, dynamic> json) {
|
SnPostPreload _$SnPostPreloadFromJson(Map<String, dynamic> json) {
|
||||||
return _SnPostPreload.fromJson(json);
|
return _SnPostPreload.fromJson(json);
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,10 @@ _$SnPostImpl _$$SnPostImplFromJson(Map<String, dynamic> json) => _$SnPostImpl(
|
|||||||
?.map((e) => SnPostTag.fromJson(e as Map<String, dynamic>))
|
?.map((e) => SnPostTag.fromJson(e as Map<String, dynamic>))
|
||||||
.toList() ??
|
.toList() ??
|
||||||
const [],
|
const [],
|
||||||
categories: json['categories'] as List<dynamic>? ?? const [],
|
categories: (json['categories'] as List<dynamic>?)
|
||||||
|
?.map((e) => SnPostCategory.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const [],
|
||||||
replies: (json['replies'] as List<dynamic>?)
|
replies: (json['replies'] as List<dynamic>?)
|
||||||
?.map((e) => SnPost.fromJson(e as Map<String, dynamic>))
|
?.map((e) => SnPost.fromJson(e as Map<String, dynamic>))
|
||||||
.toList(),
|
.toList(),
|
||||||
@ -80,7 +83,7 @@ Map<String, dynamic> _$$SnPostImplToJson(_$SnPostImpl instance) =>
|
|||||||
'alias': instance.alias,
|
'alias': instance.alias,
|
||||||
'alias_prefix': instance.aliasPrefix,
|
'alias_prefix': instance.aliasPrefix,
|
||||||
'tags': instance.tags.map((e) => e.toJson()).toList(),
|
'tags': instance.tags.map((e) => e.toJson()).toList(),
|
||||||
'categories': instance.categories,
|
'categories': instance.categories.map((e) => e.toJson()).toList(),
|
||||||
'replies': instance.replies?.map((e) => e.toJson()).toList(),
|
'replies': instance.replies?.map((e) => e.toJson()).toList(),
|
||||||
'reply_id': instance.replyId,
|
'reply_id': instance.replyId,
|
||||||
'repost_id': instance.repostId,
|
'repost_id': instance.repostId,
|
||||||
@ -127,6 +130,31 @@ Map<String, dynamic> _$$SnPostTagImplToJson(_$SnPostTagImpl instance) =>
|
|||||||
'posts': instance.posts,
|
'posts': instance.posts,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_$SnPostCategoryImpl _$$SnPostCategoryImplFromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnPostCategoryImpl(
|
||||||
|
id: (json['id'] as num).toInt(),
|
||||||
|
createdAt: DateTime.parse(json['created_at'] as String),
|
||||||
|
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||||
|
deletedAt: json['deleted_at'],
|
||||||
|
alias: json['alias'] as String,
|
||||||
|
name: json['name'] as String,
|
||||||
|
description: json['description'] as String,
|
||||||
|
posts: json['posts'],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$SnPostCategoryImplToJson(
|
||||||
|
_$SnPostCategoryImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
|
'deleted_at': instance.deletedAt,
|
||||||
|
'alias': instance.alias,
|
||||||
|
'name': instance.name,
|
||||||
|
'description': instance.description,
|
||||||
|
'posts': instance.posts,
|
||||||
|
};
|
||||||
|
|
||||||
_$SnPostPreloadImpl _$$SnPostPreloadImplFromJson(Map<String, dynamic> json) =>
|
_$SnPostPreloadImpl _$$SnPostPreloadImplFromJson(Map<String, dynamic> json) =>
|
||||||
_$SnPostPreloadImpl(
|
_$SnPostPreloadImpl(
|
||||||
thumbnail: json['thumbnail'] == null
|
thumbnail: json['thumbnail'] == null
|
||||||
|
@ -130,7 +130,7 @@ class _ChatCallPrejoinPopupState extends State<ChatCallPrejoinPopup> {
|
|||||||
Text('callCamera').tr(),
|
Text('callCamera').tr(),
|
||||||
Switch(
|
Switch(
|
||||||
value: call.enableVideo,
|
value: call.enableVideo,
|
||||||
onChanged: (value) => call.setEnableAudio(value),
|
onChanged: call.setEnableVideo,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).padding(bottom: 5),
|
).padding(bottom: 5),
|
||||||
|
@ -10,6 +10,7 @@ import 'package:surface/providers/userinfo.dart';
|
|||||||
import 'package:surface/types/chat.dart';
|
import 'package:surface/types/chat.dart';
|
||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/attachment/attachment_list.dart';
|
import 'package:surface/widgets/attachment/attachment_list.dart';
|
||||||
|
import 'package:surface/widgets/link_preview.dart';
|
||||||
import 'package:surface/widgets/markdown_content.dart';
|
import 'package:surface/widgets/markdown_content.dart';
|
||||||
import 'package:swipe_to/swipe_to.dart';
|
import 'package:swipe_to/swipe_to.dart';
|
||||||
|
|
||||||
@ -22,6 +23,7 @@ class ChatMessage extends StatelessWidget {
|
|||||||
final Function(SnChatMessage)? onReply;
|
final Function(SnChatMessage)? onReply;
|
||||||
final Function(SnChatMessage)? onEdit;
|
final Function(SnChatMessage)? onEdit;
|
||||||
final Function(SnChatMessage)? onDelete;
|
final Function(SnChatMessage)? onDelete;
|
||||||
|
|
||||||
const ChatMessage({
|
const ChatMessage({
|
||||||
super.key,
|
super.key,
|
||||||
required this.data,
|
required this.data,
|
||||||
@ -63,7 +65,7 @@ class ChatMessage extends StatelessWidget {
|
|||||||
onReply!(data);
|
onReply!(data);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (isOwner && onEdit != null)
|
if (isOwner && data.type == 'messages.new' && onEdit != null)
|
||||||
MenuItem(
|
MenuItem(
|
||||||
label: 'edit'.tr(),
|
label: 'edit'.tr(),
|
||||||
icon: Symbols.edit,
|
icon: Symbols.edit,
|
||||||
@ -71,7 +73,7 @@ class ChatMessage extends StatelessWidget {
|
|||||||
onEdit!(data);
|
onEdit!(data);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (isOwner && onDelete != null)
|
if (isOwner && data.type == 'messages.new' && onDelete != null)
|
||||||
MenuItem(
|
MenuItem(
|
||||||
label: 'delete'.tr(),
|
label: 'delete'.tr(),
|
||||||
icon: Symbols.delete,
|
icon: Symbols.delete,
|
||||||
@ -109,9 +111,7 @@ class ChatMessage extends StatelessWidget {
|
|||||||
radius: 12,
|
radius: 12,
|
||||||
).padding(right: 6),
|
).padding(right: 6),
|
||||||
Text(
|
Text(
|
||||||
(data.sender.nick?.isNotEmpty ?? false)
|
(data.sender.nick?.isNotEmpty ?? false) ? data.sender.nick! : user?.nick ?? 'unknown',
|
||||||
? data.sender.nick!
|
|
||||||
: user?.nick ?? 'unknown',
|
|
||||||
).bold(),
|
).bold(),
|
||||||
const Gap(6),
|
const Gap(6),
|
||||||
Text(
|
Text(
|
||||||
@ -123,8 +123,7 @@ class ChatMessage extends StatelessWidget {
|
|||||||
if (data.preload?.quoteEvent != null)
|
if (data.preload?.quoteEvent != null)
|
||||||
StyledWidget(Container(
|
StyledWidget(Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius:
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
const BorderRadius.all(Radius.circular(8)),
|
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Theme.of(context).dividerColor,
|
color: Theme.of(context).dividerColor,
|
||||||
width: 1,
|
width: 1,
|
||||||
@ -143,7 +142,7 @@ class ChatMessage extends StatelessWidget {
|
|||||||
onEdit: onEdit,
|
onEdit: onEdit,
|
||||||
onDelete: onDelete,
|
onDelete: onDelete,
|
||||||
),
|
),
|
||||||
)).padding(bottom: 4, top: isMerged ? 4 : 2),
|
)).padding(bottom: 4, top: 4),
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
'messages.new' => _ChatMessageText(data: data),
|
'messages.new' => _ChatMessageText(data: data),
|
||||||
_ => _ChatMessageSystemNotify(data: data),
|
_ => _ChatMessageSystemNotify(data: data),
|
||||||
@ -153,6 +152,8 @@ class ChatMessage extends StatelessWidget {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
).opacity(isPending ? 0.5 : 1),
|
).opacity(isPending ? 0.5 : 1),
|
||||||
|
if (data.body['text'] != null && (data.body['text']?.isNotEmpty ?? false))
|
||||||
|
LinkPreviewWidget(text: data.body['text']!),
|
||||||
if (data.preload?.attachments?.isNotEmpty ?? false)
|
if (data.preload?.attachments?.isNotEmpty ?? false)
|
||||||
AttachmentList(
|
AttachmentList(
|
||||||
data: data.preload!.attachments!,
|
data: data.preload!.attachments!,
|
||||||
@ -161,10 +162,7 @@ class ChatMessage extends StatelessWidget {
|
|||||||
maxHeight: 520,
|
maxHeight: 520,
|
||||||
listPadding: const EdgeInsets.only(top: 8),
|
listPadding: const EdgeInsets.only(top: 8),
|
||||||
),
|
),
|
||||||
if (!hasMerged && !isCompact)
|
if (!hasMerged && !isCompact) const Gap(12) else if (!isCompact) const Gap(6),
|
||||||
const Gap(12)
|
|
||||||
else if (!isCompact)
|
|
||||||
const Gap(6),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -174,6 +172,7 @@ class ChatMessage extends StatelessWidget {
|
|||||||
|
|
||||||
class _ChatMessageText extends StatelessWidget {
|
class _ChatMessageText extends StatelessWidget {
|
||||||
final SnChatMessage data;
|
final SnChatMessage data;
|
||||||
|
|
||||||
const _ChatMessageText({super.key, required this.data});
|
const _ChatMessageText({super.key, required this.data});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -184,6 +183,7 @@ class _ChatMessageText extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
MarkdownTextContent(
|
MarkdownTextContent(
|
||||||
content: data.body['text'],
|
content: data.body['text'],
|
||||||
|
isSelectable: true,
|
||||||
isAutoWarp: true,
|
isAutoWarp: true,
|
||||||
),
|
),
|
||||||
if (data.updatedAt != data.createdAt)
|
if (data.updatedAt != data.createdAt)
|
||||||
@ -212,6 +212,7 @@ class _ChatMessageText extends StatelessWidget {
|
|||||||
|
|
||||||
class _ChatMessageSystemNotify extends StatelessWidget {
|
class _ChatMessageSystemNotify extends StatelessWidget {
|
||||||
final SnChatMessage data;
|
final SnChatMessage data;
|
||||||
|
|
||||||
const _ChatMessageSystemNotify({super.key, required this.data});
|
const _ChatMessageSystemNotify({super.key, required this.data});
|
||||||
|
|
||||||
String _formatDuration(Duration duration) {
|
String _formatDuration(Duration duration) {
|
||||||
|
170
lib/widgets/link_preview.dart
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:marquee/marquee.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:responsive_framework/responsive_framework.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/types/link.dart';
|
||||||
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
|
import '../providers/link_preview.dart';
|
||||||
|
|
||||||
|
class LinkPreviewWidget extends StatefulWidget {
|
||||||
|
final String text;
|
||||||
|
|
||||||
|
const LinkPreviewWidget({super.key, required this.text});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LinkPreviewWidget> createState() => _LinkPreviewWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LinkPreviewWidgetState extends State<LinkPreviewWidget> {
|
||||||
|
final List<SnLinkMeta> _links = List.empty(growable: true);
|
||||||
|
|
||||||
|
Future<void> _getLinkMeta() async {
|
||||||
|
final linkRegex = RegExp(r'https?:\/\/[^\s/$.?#].[^\s]*');
|
||||||
|
final links = linkRegex.allMatches(widget.text).map((e) => e.group(0)).toSet();
|
||||||
|
|
||||||
|
final lp = context.read<SnLinkPreviewProvider>();
|
||||||
|
|
||||||
|
final List<Future<SnLinkMeta?>> futures = links.where((e) => e != null).map((e) => lp.getLinkMeta(e!)).toList();
|
||||||
|
final results = await Future.wait(futures);
|
||||||
|
|
||||||
|
_links.addAll(results.where((e) => e != null).map((e) => e!).toList());
|
||||||
|
if (_links.isNotEmpty && mounted) setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_getLinkMeta();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (_links.isEmpty) return const SizedBox.shrink();
|
||||||
|
|
||||||
|
return Wrap(
|
||||||
|
spacing: 8,
|
||||||
|
runSpacing: 8,
|
||||||
|
children: _links.map((e) => _LinkPreviewEntry(meta: e)).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LinkPreviewEntry extends StatelessWidget {
|
||||||
|
final SnLinkMeta meta;
|
||||||
|
|
||||||
|
const _LinkPreviewEntry({
|
||||||
|
super.key,
|
||||||
|
required this.meta,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: ResponsiveBreakpoints.of(context).smallerOrEqualTo(MOBILE) ? double.infinity : 480,
|
||||||
|
),
|
||||||
|
child: GestureDetector(
|
||||||
|
child: Card(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (meta.image != null)
|
||||||
|
Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 4),
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 16 / 9,
|
||||||
|
child: ClipRRect(
|
||||||
|
child: AutoResizeUniversalImage(
|
||||||
|
meta.image!,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 48,
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
if (meta.icon?.isNotEmpty ?? false)
|
||||||
|
StyledWidget(
|
||||||
|
meta.icon!.endsWith('.svg')
|
||||||
|
? SvgPicture.network(meta.icon!)
|
||||||
|
: UniversalImage(
|
||||||
|
meta.icon!,
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
cacheHeight: 36,
|
||||||
|
cacheWidth: 36,
|
||||||
|
),
|
||||||
|
).padding(all: 4, right: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 24,
|
||||||
|
child: ((meta.title?.length ?? 0) > 32)
|
||||||
|
? Marquee(
|
||||||
|
text: meta.title ?? 'unknown'.tr(),
|
||||||
|
style: TextStyle(fontSize: 17),
|
||||||
|
scrollAxis: Axis.horizontal,
|
||||||
|
showFadingOnlyWhenScrolling: true,
|
||||||
|
pauseAfterRound: const Duration(seconds: 3),
|
||||||
|
)
|
||||||
|
: Text(
|
||||||
|
meta.title ?? 'unknown'.tr(),
|
||||||
|
style: TextStyle(fontSize: 17),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (meta.siteName != null)
|
||||||
|
Text(
|
||||||
|
meta.siteName!,
|
||||||
|
style: TextStyle(fontSize: 13, height: 0.9),
|
||||||
|
).fontSize(11),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(6),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 16),
|
||||||
|
),
|
||||||
|
if (meta.description != null)
|
||||||
|
Text(
|
||||||
|
meta.description!,
|
||||||
|
maxLines: 3,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
).padding(horizontal: 16, bottom: 8),
|
||||||
|
Text(
|
||||||
|
meta.url,
|
||||||
|
style: GoogleFonts.roboto(fontSize: 11, height: 0.9),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
).opacity(0.75).padding(horizontal: 16),
|
||||||
|
const Gap(4),
|
||||||
|
Text(
|
||||||
|
'poweredBy'.tr(args: ['HyperNet.Reader']),
|
||||||
|
style: GoogleFonts.roboto(fontSize: 11, height: 0.9),
|
||||||
|
).opacity(0.75).padding(horizontal: 16),
|
||||||
|
const Gap(16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
launchUrlString(meta.url, mode: LaunchMode.externalApplication);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:responsive_framework/responsive_framework.dart';
|
import 'package:responsive_framework/responsive_framework.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
@ -17,6 +17,8 @@ import 'package:responsive_framework/responsive_framework.dart';
|
|||||||
import 'package:screenshot/screenshot.dart';
|
import 'package:screenshot/screenshot.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/providers/config.dart';
|
||||||
|
import 'package:surface/providers/link_preview.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/providers/userinfo.dart';
|
import 'package:surface/providers/userinfo.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
@ -24,6 +26,7 @@ import 'package:surface/types/reaction.dart';
|
|||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/attachment/attachment_list.dart';
|
import 'package:surface/widgets/attachment/attachment_list.dart';
|
||||||
import 'package:surface/widgets/dialog.dart';
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:surface/widgets/link_preview.dart';
|
||||||
import 'package:surface/widgets/markdown_content.dart';
|
import 'package:surface/widgets/markdown_content.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:surface/widgets/post/post_comment_list.dart';
|
import 'package:surface/widgets/post/post_comment_list.dart';
|
||||||
@ -82,6 +85,8 @@ class PostItem extends StatelessWidget {
|
|||||||
child: MultiProvider(
|
child: MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
Provider<SnNetworkProvider>(create: (_) => context.read()),
|
Provider<SnNetworkProvider>(create: (_) => context.read()),
|
||||||
|
Provider<SnLinkPreviewProvider>(create: (_) => context.read()),
|
||||||
|
ChangeNotifierProvider<ConfigProvider>(create: (_) => context.read()),
|
||||||
],
|
],
|
||||||
child: ResponsiveBreakpoints.builder(
|
child: ResponsiveBreakpoints.builder(
|
||||||
breakpoints: ResponsiveBreakpoints.of(context).breakpoints,
|
breakpoints: ResponsiveBreakpoints.of(context).breakpoints,
|
||||||
@ -132,6 +137,7 @@ class PostItem extends StatelessWidget {
|
|||||||
_PostContentHeader(
|
_PostContentHeader(
|
||||||
data: data,
|
data: data,
|
||||||
isAuthor: isAuthor,
|
isAuthor: isAuthor,
|
||||||
|
isRelativeDate: !showFullPost,
|
||||||
onShare: () => _doShare(context),
|
onShare: () => _doShare(context),
|
||||||
onShareImage: () => _doShareViaPicture(context),
|
onShareImage: () => _doShareViaPicture(context),
|
||||||
onDeleted: () {
|
onDeleted: () {
|
||||||
@ -173,6 +179,7 @@ class PostItem extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
if (data.visibility > 0) _PostVisibilityHint(data: data),
|
if (data.visibility > 0) _PostVisibilityHint(data: data),
|
||||||
_PostTruncatedHint(data: data),
|
_PostTruncatedHint(data: data),
|
||||||
|
if (data.tags.isNotEmpty) _PostTagsList(data: data),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 12),
|
).padding(horizontal: 12),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
@ -180,7 +187,6 @@ class PostItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text('postArticle').tr().fontSize(13).opacity(0.75).padding(horizontal: 24, bottom: 8),
|
Text('postArticle').tr().fontSize(13).opacity(0.75).padding(horizontal: 24, bottom: 8),
|
||||||
if (data.tags.isNotEmpty) _PostTagsList(data: data).padding(horizontal: 16, bottom: 6),
|
|
||||||
_PostBottomAction(
|
_PostBottomAction(
|
||||||
data: data,
|
data: data,
|
||||||
showComments: showComments,
|
showComments: showComments,
|
||||||
@ -204,6 +210,7 @@ class PostItem extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
_PostContentHeader(
|
_PostContentHeader(
|
||||||
isAuthor: isAuthor,
|
isAuthor: isAuthor,
|
||||||
|
isRelativeDate: !showFullPost,
|
||||||
data: data,
|
data: data,
|
||||||
showMenu: showMenu,
|
showMenu: showMenu,
|
||||||
onShare: () => _doShare(context),
|
onShare: () => _doShare(context),
|
||||||
@ -217,8 +224,10 @@ class PostItem extends StatelessWidget {
|
|||||||
data: data,
|
data: data,
|
||||||
isEnlarge: data.type == 'article' && showFullPost,
|
isEnlarge: data.type == 'article' && showFullPost,
|
||||||
).padding(horizontal: 16, bottom: 8),
|
).padding(horizontal: 16, bottom: 8),
|
||||||
|
if (data.body['content']?.isNotEmpty ?? false)
|
||||||
_PostContentBody(
|
_PostContentBody(
|
||||||
data: data,
|
data: data,
|
||||||
|
isSelectable: showFullPost,
|
||||||
isEnlarge: data.type == 'article' && showFullPost,
|
isEnlarge: data.type == 'article' && showFullPost,
|
||||||
).padding(horizontal: 16, bottom: 6),
|
).padding(horizontal: 16, bottom: 6),
|
||||||
if (data.repostTo != null)
|
if (data.repostTo != null)
|
||||||
@ -236,7 +245,7 @@ class PostItem extends StatelessWidget {
|
|||||||
horizontal: 16,
|
horizontal: 16,
|
||||||
vertical: 4,
|
vertical: 4,
|
||||||
),
|
),
|
||||||
if (data.tags.isNotEmpty) _PostTagsList(data: data).padding(horizontal: 16, bottom: 6),
|
if (data.tags.isNotEmpty) _PostTagsList(data: data).padding(horizontal: 16, top: 4, bottom: 6),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -247,6 +256,10 @@ class PostItem extends StatelessWidget {
|
|||||||
maxHeight: 560,
|
maxHeight: 560,
|
||||||
listPadding: const EdgeInsets.symmetric(horizontal: 12),
|
listPadding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
),
|
),
|
||||||
|
if (data.body['content'] != null)
|
||||||
|
LinkPreviewWidget(
|
||||||
|
text: data.body['content'],
|
||||||
|
).padding(horizontal: 4),
|
||||||
Container(
|
Container(
|
||||||
constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity),
|
constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity),
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -312,6 +325,7 @@ class PostShareImageWidget extends StatelessWidget {
|
|||||||
data: data,
|
data: data,
|
||||||
isEnlarge: data.type == 'article',
|
isEnlarge: data.type == 'article',
|
||||||
).padding(horizontal: 16, bottom: 8),
|
).padding(horizontal: 16, bottom: 8),
|
||||||
|
if (data.body['content']?.isNotEmpty ?? false)
|
||||||
_PostContentBody(
|
_PostContentBody(
|
||||||
data: data,
|
data: data,
|
||||||
isEnlarge: data.type == 'article',
|
isEnlarge: data.type == 'article',
|
||||||
@ -327,6 +341,10 @@ class PostShareImageWidget extends StatelessWidget {
|
|||||||
data: data.preload!.attachments!,
|
data: data.preload!.attachments!,
|
||||||
isFlatted: true,
|
isFlatted: true,
|
||||||
).padding(horizontal: 16, bottom: 8),
|
).padding(horizontal: 16, bottom: 8),
|
||||||
|
if (data.body['content'] != null)
|
||||||
|
LinkPreviewWidget(
|
||||||
|
text: data.body['content'],
|
||||||
|
).padding(horizontal: 4),
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -396,7 +414,7 @@ class PostShareImageWidget extends StatelessWidget {
|
|||||||
size: Size(28, 28),
|
size: Size(28, 28),
|
||||||
),
|
),
|
||||||
eyeStyle: QrEyeStyle(
|
eyeStyle: QrEyeStyle(
|
||||||
eyeShape: QrEyeShape.square,
|
eyeShape: QrEyeShape.circle,
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
),
|
),
|
||||||
dataModuleStyle: QrDataModuleStyle(
|
dataModuleStyle: QrDataModuleStyle(
|
||||||
@ -444,6 +462,7 @@ class _PostBottomAction extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
if (showReactions || showComments)
|
if (showReactions || showComments)
|
||||||
Row(
|
Row(
|
||||||
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
if (showReactions)
|
if (showReactions)
|
||||||
InkWell(
|
InkWell(
|
||||||
@ -509,8 +528,7 @@ class _PostBottomAction extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
].expand((ele) => [ele, const Gap(8)]).toList()
|
],
|
||||||
..removeLast(),
|
|
||||||
),
|
),
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: onShare,
|
onTap: onShare,
|
||||||
@ -850,16 +868,19 @@ class _PostContentHeader extends StatelessWidget {
|
|||||||
class _PostContentBody extends StatelessWidget {
|
class _PostContentBody extends StatelessWidget {
|
||||||
final SnPost data;
|
final SnPost data;
|
||||||
final bool isEnlarge;
|
final bool isEnlarge;
|
||||||
|
final bool isSelectable;
|
||||||
|
|
||||||
const _PostContentBody({
|
const _PostContentBody({
|
||||||
required this.data,
|
required this.data,
|
||||||
this.isEnlarge = false,
|
this.isEnlarge = false,
|
||||||
|
this.isSelectable = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (data.body['content'] == null) return const SizedBox.shrink();
|
if (data.body['content'] == null) return const SizedBox.shrink();
|
||||||
return MarkdownTextContent(
|
return MarkdownTextContent(
|
||||||
|
isSelectable: isSelectable,
|
||||||
textScaler: isEnlarge ? TextScaler.linear(1.1) : null,
|
textScaler: isEnlarge ? TextScaler.linear(1.1) : null,
|
||||||
content: data.body['content'],
|
content: data.body['content'],
|
||||||
attachments: data.preload?.attachments,
|
attachments: data.preload?.attachments,
|
||||||
@ -945,23 +966,69 @@ class _PostTagsList extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Wrap(
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Wrap(
|
||||||
|
spacing: 4,
|
||||||
|
runSpacing: 4,
|
||||||
|
children: data.categories
|
||||||
|
.map(
|
||||||
|
(ele) => InkWell(
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.category, size: 20),
|
||||||
|
const Gap(4),
|
||||||
|
Text(
|
||||||
|
'postCategory${ele.alias.capitalize()}'.trExists()
|
||||||
|
? 'postCategory${ele.alias.capitalize()}'.tr()
|
||||||
|
: ele.alias,
|
||||||
|
style: GoogleFonts.robotoMono(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).pushNamed(
|
||||||
|
'postSearch',
|
||||||
|
queryParameters: {
|
||||||
|
'categories': ele.alias,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
).opacity(0.8),
|
||||||
|
Wrap(
|
||||||
spacing: 4,
|
spacing: 4,
|
||||||
runSpacing: 4,
|
runSpacing: 4,
|
||||||
children: data.tags
|
children: data.tags
|
||||||
.map(
|
.map(
|
||||||
(ele) => InkWell(
|
(ele) => InkWell(
|
||||||
child: Text(
|
child: Row(
|
||||||
'#${ele.alias}',
|
mainAxisSize: MainAxisSize.min,
|
||||||
style: TextStyle(
|
children: [
|
||||||
decoration: TextDecoration.underline,
|
const Icon(Symbols.label, size: 20),
|
||||||
|
const Gap(4),
|
||||||
|
Text(ele.alias, style: GoogleFonts.robotoMono()),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
).fontSize(13),
|
onTap: () {
|
||||||
onTap: () {},
|
GoRouter.of(context).pushNamed(
|
||||||
|
'postSearch',
|
||||||
|
queryParameters: {
|
||||||
|
'tags': ele.alias,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
).opacity(0.8);
|
).opacity(0.8),
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1002,6 +1069,7 @@ class _PostTruncatedHint extends StatelessWidget {
|
|||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
child: Row(
|
child: Row(
|
||||||
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
if (data.body['content_length'] != null)
|
if (data.body['content_length'] != null)
|
||||||
Row(
|
Row(
|
||||||
@ -1014,7 +1082,7 @@ class _PostTruncatedHint extends StatelessWidget {
|
|||||||
).inSeconds}s',
|
).inSeconds}s',
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
).padding(right: 8),
|
),
|
||||||
if (data.body['content_length'] != null)
|
if (data.body['content_length'] != null)
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
|
@ -189,16 +189,19 @@ class PostMediaPendingList extends StatelessWidget {
|
|||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: 1,
|
aspectRatio: 1,
|
||||||
child: switch (thumbnail!.type) {
|
child: switch (thumbnail!.type) {
|
||||||
PostWriteMediaType.image => LayoutBuilder(builder: (context, constraints) {
|
PostWriteMediaType.image => Container(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
|
child: LayoutBuilder(builder: (context, constraints) {
|
||||||
return Image(
|
return Image(
|
||||||
image: thumbnail!.getImageProvider(
|
image: thumbnail!.getImageProvider(
|
||||||
context,
|
context,
|
||||||
width: (constraints.maxWidth * devicePixelRatio).round(),
|
width: (constraints.maxWidth * devicePixelRatio).round(),
|
||||||
height: (constraints.maxHeight * devicePixelRatio).round(),
|
height: (constraints.maxHeight * devicePixelRatio).round(),
|
||||||
)!,
|
)!,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.contain,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
),
|
||||||
_ => Container(
|
_ => Container(
|
||||||
color: Theme.of(context).colorScheme.surface,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
child: const Icon(Symbols.docs).center(),
|
child: const Icon(Symbols.docs).center(),
|
||||||
@ -236,18 +239,21 @@ class PostMediaPendingList extends StatelessWidget {
|
|||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: 1,
|
aspectRatio: 1,
|
||||||
child: switch (media.type) {
|
child: switch (media.type) {
|
||||||
PostWriteMediaType.image => LayoutBuilder(builder: (context, constraints) {
|
PostWriteMediaType.image => Container(
|
||||||
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
|
child: LayoutBuilder(builder: (context, constraints) {
|
||||||
return Image(
|
return Image(
|
||||||
image: media.getImageProvider(
|
image: media.getImageProvider(
|
||||||
context,
|
context,
|
||||||
width: (constraints.maxWidth * devicePixelRatio).round(),
|
width: (constraints.maxWidth * devicePixelRatio).round(),
|
||||||
height: (constraints.maxHeight * devicePixelRatio).round(),
|
height: (constraints.maxHeight * devicePixelRatio).round(),
|
||||||
)!,
|
)!,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.contain,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
),
|
||||||
_ => Container(
|
_ => Container(
|
||||||
color: Theme.of(context).colorScheme.surface,
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
child: const Icon(Symbols.docs).center(),
|
child: const Icon(Symbols.docs).center(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -83,7 +83,9 @@ class PostMetaEditor extends StatelessWidget {
|
|||||||
return ListenableBuilder(
|
return ListenableBuilder(
|
||||||
listenable: controller,
|
listenable: controller,
|
||||||
builder: (context, _) {
|
builder: (context, _) {
|
||||||
return Column(
|
return SingleChildScrollView(
|
||||||
|
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom + 8),
|
||||||
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
TextField(
|
TextField(
|
||||||
controller: controller.titleController,
|
controller: controller.titleController,
|
||||||
@ -114,6 +116,26 @@ class PostMetaEditor extends StatelessWidget {
|
|||||||
controller.setTags(value);
|
controller.setTags(value);
|
||||||
},
|
},
|
||||||
).padding(horizontal: 24),
|
).padding(horizontal: 24),
|
||||||
|
const Gap(4),
|
||||||
|
PostCategoriesField(
|
||||||
|
initialCategories: controller.categories,
|
||||||
|
labelText: 'fieldPostCategories'.tr(),
|
||||||
|
onUpdate: (value) {
|
||||||
|
controller.setCategories(value);
|
||||||
|
},
|
||||||
|
).padding(horizontal: 24),
|
||||||
|
const Gap(4),
|
||||||
|
TextField(
|
||||||
|
controller: controller.aliasController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'fieldPostAlias'.tr(),
|
||||||
|
helperText: 'fieldPostAliasHint'.tr(),
|
||||||
|
helperMaxLines: 2,
|
||||||
|
border: UnderlineInputBorder(),
|
||||||
|
),
|
||||||
|
onTapOutside: (_) =>
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
).padding(horizontal: 24),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
ListTile(
|
ListTile(
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
|
||||||
@ -231,7 +253,8 @@ class PostMetaEditor extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).padding(vertical: 8);
|
).padding(vertical: 8),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import 'package:material_symbols_icons/symbols.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/controllers/post_write_controller.dart';
|
import 'package:surface/controllers/post_write_controller.dart';
|
||||||
|
import 'package:surface/providers/config.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
@ -16,6 +17,7 @@ import 'package:surface/widgets/loading_indicator.dart';
|
|||||||
class PostMiniEditor extends StatefulWidget {
|
class PostMiniEditor extends StatefulWidget {
|
||||||
final int? postReplyId;
|
final int? postReplyId;
|
||||||
final Function? onPost;
|
final Function? onPost;
|
||||||
|
|
||||||
const PostMiniEditor({super.key, this.postReplyId, this.onPost});
|
const PostMiniEditor({super.key, this.postReplyId, this.onPost});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -26,6 +28,7 @@ class _PostMiniEditorState extends State<PostMiniEditor> {
|
|||||||
final PostWriteController _writeController = PostWriteController();
|
final PostWriteController _writeController = PostWriteController();
|
||||||
|
|
||||||
bool _isFetching = false;
|
bool _isFetching = false;
|
||||||
|
|
||||||
bool get _isLoading => _isFetching || _writeController.isLoading;
|
bool get _isLoading => _isFetching || _writeController.isLoading;
|
||||||
|
|
||||||
List<SnPublisher>? _publishers;
|
List<SnPublisher>? _publishers;
|
||||||
@ -35,11 +38,14 @@ class _PostMiniEditorState extends State<PostMiniEditor> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final config = context.read<ConfigProvider>();
|
||||||
final resp = await sn.client.get('/cgi/co/publishers/me');
|
final resp = await sn.client.get('/cgi/co/publishers/me');
|
||||||
_publishers = List<SnPublisher>.from(
|
_publishers = List<SnPublisher>.from(
|
||||||
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [],
|
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [],
|
||||||
);
|
);
|
||||||
_writeController.setPublisher(_publishers?.firstOrNull);
|
final beforeId = config.prefs.getInt('int_last_publisher_id');
|
||||||
|
_writeController
|
||||||
|
.setPublisher(_publishers?.where((ele) => ele.id == beforeId).firstOrNull ?? _publishers?.firstOrNull);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
@ -93,17 +99,11 @@ class _PostMiniEditorState extends State<PostMiniEditor> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment:
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
Text(item.nick).textStyle(
|
Text(item.nick).textStyle(Theme.of(context).textTheme.bodyMedium!),
|
||||||
Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.bodyMedium!),
|
|
||||||
Text('@${item.name}')
|
Text('@${item.name}')
|
||||||
.textStyle(Theme.of(context)
|
.textStyle(Theme.of(context).textTheme.bodySmall!)
|
||||||
.textTheme
|
|
||||||
.bodySmall!)
|
|
||||||
.fontSize(12),
|
.fontSize(12),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -120,8 +120,7 @@ class _PostMiniEditorState extends State<PostMiniEditor> {
|
|||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 16,
|
radius: 16,
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
foregroundColor:
|
foregroundColor: Theme.of(context).colorScheme.onSurface,
|
||||||
Theme.of(context).colorScheme.onSurface,
|
|
||||||
child: const Icon(Symbols.add),
|
child: const Icon(Symbols.add),
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
@ -130,8 +129,7 @@ class _PostMiniEditorState extends State<PostMiniEditor> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('publishersNew').tr().textStyle(
|
Text('publishersNew').tr().textStyle(Theme.of(context).textTheme.bodyMedium!),
|
||||||
Theme.of(context).textTheme.bodyMedium!),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -142,9 +140,7 @@ class _PostMiniEditorState extends State<PostMiniEditor> {
|
|||||||
value: _writeController.publisher,
|
value: _writeController.publisher,
|
||||||
onChanged: (SnPublisher? value) {
|
onChanged: (SnPublisher? value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
GoRouter.of(context)
|
GoRouter.of(context).pushNamed('accountPublisherNew').then((value) {
|
||||||
.pushNamed('accountPublisherNew')
|
|
||||||
.then((value) {
|
|
||||||
if (value == true) {
|
if (value == true) {
|
||||||
_publishers = null;
|
_publishers = null;
|
||||||
_fetchPublishers();
|
_fetchPublishers();
|
||||||
@ -152,6 +148,8 @@ class _PostMiniEditorState extends State<PostMiniEditor> {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
_writeController.setPublisher(value);
|
_writeController.setPublisher(value);
|
||||||
|
final config = context.read<ConfigProvider>();
|
||||||
|
config.prefs.setInt('int_last_publisher_id', value.id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
buttonStyleData: const ButtonStyleData(
|
buttonStyleData: const ButtonStyleData(
|
||||||
@ -178,8 +176,7 @@ class _PostMiniEditorState extends State<PostMiniEditor> {
|
|||||||
),
|
),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
),
|
),
|
||||||
onTapOutside: (_) =>
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
@ -188,8 +185,7 @@ class _PostMiniEditorState extends State<PostMiniEditor> {
|
|||||||
TweenAnimationBuilder<double>(
|
TweenAnimationBuilder<double>(
|
||||||
tween: Tween(begin: 0, end: _writeController.progress),
|
tween: Tween(begin: 0, end: _writeController.progress),
|
||||||
duration: Duration(milliseconds: 300),
|
duration: Duration(milliseconds: 300),
|
||||||
builder: (context, value, _) =>
|
builder: (context, value, _) => LinearProgressIndicator(value: value, minHeight: 2),
|
||||||
LinearProgressIndicator(value: value, minHeight: 2),
|
|
||||||
)
|
)
|
||||||
else if (_writeController.isBusy)
|
else if (_writeController.isBusy)
|
||||||
const LinearProgressIndicator(value: null, minHeight: 2),
|
const LinearProgressIndicator(value: null, minHeight: 2),
|
||||||
@ -206,18 +202,16 @@ class _PostMiniEditorState extends State<PostMiniEditor> {
|
|||||||
'postEditor',
|
'postEditor',
|
||||||
pathParameters: {'mode': 'stories'},
|
pathParameters: {'mode': 'stories'},
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
if (widget.postReplyId != null)
|
if (widget.postReplyId != null) 'replying': widget.postReplyId.toString(),
|
||||||
'replying': widget.postReplyId.toString(),
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: (_writeController.isBusy ||
|
onPressed: (_writeController.isBusy || _writeController.publisher == null)
|
||||||
_writeController.publisher == null)
|
|
||||||
? null
|
? null
|
||||||
: () {
|
: () {
|
||||||
_writeController.post(context).then((_) {
|
_writeController.sendPost(context).then((_) {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
if (widget.onPost != null) widget.onPost!();
|
if (widget.onPost != null) widget.onPost!();
|
||||||
context.showSnackbar('postPosted'.tr());
|
context.showSnackbar('postPosted'.tr());
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
|
||||||
class PostTagsField extends StatefulWidget {
|
class PostTagsField extends StatefulWidget {
|
||||||
final List<String>? initialTags;
|
final List<String>? initialTags;
|
||||||
@ -21,9 +23,9 @@ class PostTagsField extends StatefulWidget {
|
|||||||
State<PostTagsField> createState() => _PostTagsFieldState();
|
State<PostTagsField> createState() => _PostTagsFieldState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PostTagsFieldState extends State<PostTagsField> {
|
const List<String> kTagsDividers = [' ', ','];
|
||||||
static const List<String> kTagsDividers = [' ', ','];
|
|
||||||
|
|
||||||
|
class _PostTagsFieldState extends State<PostTagsField> {
|
||||||
late final _Debounceable<List<String>?, String> _debouncedSearch;
|
late final _Debounceable<List<String>?, String> _debouncedSearch;
|
||||||
|
|
||||||
final List<String> _currentTags = List.empty(growable: true);
|
final List<String> _currentTags = List.empty(growable: true);
|
||||||
@ -100,8 +102,7 @@ class _PostTagsFieldState extends State<PostTagsField> {
|
|||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
margin: const EdgeInsets.only(right: 8),
|
margin: const EdgeInsets.only(right: 8),
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 4.0),
|
||||||
horizontal: 10.0, vertical: 4.0),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
@ -155,6 +156,141 @@ class _PostTagsFieldState extends State<PostTagsField> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PostCategoriesField extends StatefulWidget {
|
||||||
|
final List<String>? initialCategories;
|
||||||
|
final String labelText;
|
||||||
|
final Function(List<String>) onUpdate;
|
||||||
|
|
||||||
|
const PostCategoriesField({
|
||||||
|
super.key,
|
||||||
|
this.initialCategories,
|
||||||
|
required this.labelText,
|
||||||
|
required this.onUpdate,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PostCategoriesField> createState() => _PostCategoriesFieldState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PostCategoriesFieldState extends State<PostCategoriesField> {
|
||||||
|
late final _Debounceable<List<String>?, String> _debouncedSearch;
|
||||||
|
|
||||||
|
final List<String> _currentCategories = List.empty(growable: true);
|
||||||
|
|
||||||
|
String? _currentSearchProbe;
|
||||||
|
List<String> _lastAutocompleteResult = List.empty();
|
||||||
|
TextEditingController? _textEditingController;
|
||||||
|
|
||||||
|
Future<List<String>?> _searchCategories(String probe) async {
|
||||||
|
_currentSearchProbe = probe;
|
||||||
|
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get(
|
||||||
|
'/cgi/co/categories?take=10&probe=$_currentSearchProbe',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (_currentSearchProbe != probe) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
_currentSearchProbe = null;
|
||||||
|
|
||||||
|
return resp.data.map((x) => x['alias']).toList().cast<String>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_debouncedSearch = _debounce<List<String>?, String>(_searchCategories);
|
||||||
|
if (widget.initialCategories != null) {
|
||||||
|
_currentCategories.addAll(widget.initialCategories!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Autocomplete<String>(
|
||||||
|
optionsBuilder: (TextEditingValue textEditingValue) async {
|
||||||
|
final result = await _debouncedSearch(textEditingValue.text);
|
||||||
|
if (result == null) {
|
||||||
|
return _lastAutocompleteResult;
|
||||||
|
}
|
||||||
|
_lastAutocompleteResult = result;
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
onSelected: (String value) {
|
||||||
|
if (value.isEmpty) return;
|
||||||
|
if (!_currentCategories.contains(value)) {
|
||||||
|
setState(() => _currentCategories.add(value));
|
||||||
|
}
|
||||||
|
_textEditingController?.clear();
|
||||||
|
widget.onUpdate(_currentCategories);
|
||||||
|
},
|
||||||
|
fieldViewBuilder: (context, controller, focusNode, onSubmitted) {
|
||||||
|
_textEditingController = controller;
|
||||||
|
return TextField(
|
||||||
|
controller: controller,
|
||||||
|
focusNode: focusNode,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
label: Text(widget.labelText),
|
||||||
|
border: const UnderlineInputBorder(),
|
||||||
|
prefixIconConstraints: BoxConstraints(
|
||||||
|
maxWidth: MediaQuery.of(context).size.width * 0.75,
|
||||||
|
),
|
||||||
|
prefixIcon: _currentCategories.isNotEmpty
|
||||||
|
? SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Row(
|
||||||
|
children: _currentCategories.map((String category) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.all(
|
||||||
|
Radius.circular(20.0),
|
||||||
|
),
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
margin: const EdgeInsets.only(right: 8),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 4.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
InkWell(
|
||||||
|
child: Text(
|
||||||
|
'postCategory${category.capitalize()}'.trExists()
|
||||||
|
? 'postCategory${category.capitalize()}'.tr()
|
||||||
|
: '#$category',
|
||||||
|
style: const TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(4),
|
||||||
|
InkWell(
|
||||||
|
child: const Icon(
|
||||||
|
Icons.cancel,
|
||||||
|
size: 14.0,
|
||||||
|
color: Color.fromARGB(255, 233, 233, 233),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
setState(() => _currentCategories.remove(category));
|
||||||
|
widget.onUpdate(_currentCategories);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
onSubmitted: (_) {
|
||||||
|
onSubmitted();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
typedef _Debounceable<S, T> = Future<S?> Function(T parameter);
|
typedef _Debounceable<S, T> = Future<S?> Function(T parameter);
|
||||||
|
|
||||||
_Debounceable<S, T> _debounce<S, T>(_Debounceable<S?, T> function) {
|
_Debounceable<S, T> _debounce<S, T>(_Debounceable<S?, T> function) {
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:flutter_animate/flutter_animate.dart';
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
|
|
||||||
|
// Keep this import to make the web image render work
|
||||||
|
import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart';
|
||||||
|
import 'package:surface/providers/config.dart';
|
||||||
|
|
||||||
class UniversalImage extends StatelessWidget {
|
class UniversalImage extends StatelessWidget {
|
||||||
final String url;
|
final String url;
|
||||||
final double? width, height;
|
final double? width, height;
|
||||||
@ -14,6 +17,7 @@ class UniversalImage extends StatelessWidget {
|
|||||||
final bool noProgressIndicator;
|
final bool noProgressIndicator;
|
||||||
final bool noErrorWidget;
|
final bool noErrorWidget;
|
||||||
final double? cacheWidth, cacheHeight;
|
final double? cacheWidth, cacheHeight;
|
||||||
|
final FilterQuality? filterQuality;
|
||||||
|
|
||||||
const UniversalImage(
|
const UniversalImage(
|
||||||
this.url, {
|
this.url, {
|
||||||
@ -25,18 +29,20 @@ class UniversalImage extends StatelessWidget {
|
|||||||
this.noErrorWidget = false,
|
this.noErrorWidget = false,
|
||||||
this.cacheWidth,
|
this.cacheWidth,
|
||||||
this.cacheHeight,
|
this.cacheHeight,
|
||||||
|
this.filterQuality,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||||
final double? resizeHeight =
|
final double? resizeHeight = cacheHeight != null ? (cacheHeight! * devicePixelRatio) : null;
|
||||||
cacheHeight != null ? (cacheHeight! * devicePixelRatio) : null;
|
final double? resizeWidth = cacheWidth != null ? (cacheWidth! * devicePixelRatio) : null;
|
||||||
final double? resizeWidth =
|
|
||||||
cacheWidth != null ? (cacheWidth! * devicePixelRatio) : null;
|
|
||||||
|
|
||||||
return Image(
|
return Image(
|
||||||
image: ResizeImage(
|
filterQuality: filterQuality ?? context.read<ConfigProvider>().imageQuality,
|
||||||
|
image: kIsWeb
|
||||||
|
? UniversalImage.provider(url)
|
||||||
|
: ResizeImage(
|
||||||
UniversalImage.provider(url),
|
UniversalImage.provider(url),
|
||||||
width: resizeWidth?.round(),
|
width: resizeWidth?.round(),
|
||||||
height: resizeHeight?.round(),
|
height: resizeHeight?.round(),
|
||||||
@ -47,23 +53,19 @@ class UniversalImage extends StatelessWidget {
|
|||||||
fit: fit,
|
fit: fit,
|
||||||
loadingBuilder: noProgressIndicator
|
loadingBuilder: noProgressIndicator
|
||||||
? null
|
? null
|
||||||
: (BuildContext context, Widget child,
|
: (BuildContext context, Widget child, ImageChunkEvent? loadingProgress) {
|
||||||
ImageChunkEvent? loadingProgress) {
|
|
||||||
if (loadingProgress == null) return child;
|
if (loadingProgress == null) return child;
|
||||||
return Center(
|
return Center(
|
||||||
child: TweenAnimationBuilder(
|
child: TweenAnimationBuilder(
|
||||||
tween: Tween(
|
tween: Tween(
|
||||||
begin: 0,
|
begin: 0,
|
||||||
end: loadingProgress.expectedTotalBytes != null
|
end: loadingProgress.expectedTotalBytes != null
|
||||||
? loadingProgress.cumulativeBytesLoaded /
|
? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes!
|
||||||
loadingProgress.expectedTotalBytes!
|
|
||||||
: 0,
|
: 0,
|
||||||
),
|
),
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
builder: (context, value, _) => CircularProgressIndicator(
|
builder: (context, value, _) => CircularProgressIndicator(
|
||||||
value: loadingProgress.expectedTotalBytes != null
|
value: loadingProgress.expectedTotalBytes != null ? value.toDouble() : null,
|
||||||
? value.toDouble()
|
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -94,10 +96,13 @@ class UniversalImage extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static ImageProvider provider(String url) {
|
static ImageProvider provider(String url) {
|
||||||
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS || Platform.isMacOS)) {
|
// This place used to use network image or cached network image depending on the platform.
|
||||||
return CachedNetworkImageProvider(url);
|
// But now the cached network image is working on every platform.
|
||||||
}
|
// So we just use it now.
|
||||||
return NetworkImage(url);
|
return CachedNetworkImageProvider(
|
||||||
|
url,
|
||||||
|
imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import firebase_messaging
|
|||||||
import flutter_udid
|
import flutter_udid
|
||||||
import flutter_webrtc
|
import flutter_webrtc
|
||||||
import gal
|
import gal
|
||||||
|
import in_app_review
|
||||||
import livekit_client
|
import livekit_client
|
||||||
import media_kit_libs_macos_video
|
import media_kit_libs_macos_video
|
||||||
import media_kit_video
|
import media_kit_video
|
||||||
@ -41,6 +42,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
FlutterUdidPlugin.register(with: registry.registrar(forPlugin: "FlutterUdidPlugin"))
|
FlutterUdidPlugin.register(with: registry.registrar(forPlugin: "FlutterUdidPlugin"))
|
||||||
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
|
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
|
||||||
GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin"))
|
GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin"))
|
||||||
|
InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin"))
|
||||||
LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin"))
|
LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin"))
|
||||||
MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin"))
|
MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin"))
|
||||||
MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin"))
|
MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin"))
|
||||||
|
@ -26,7 +26,7 @@ PODS:
|
|||||||
- Firebase/Analytics (= 11.4.0)
|
- Firebase/Analytics (= 11.4.0)
|
||||||
- firebase_core
|
- firebase_core
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- firebase_core (3.8.1):
|
- firebase_core (3.9.0):
|
||||||
- Firebase/CoreOnly (~> 11.4.0)
|
- Firebase/CoreOnly (~> 11.4.0)
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- firebase_messaging (15.1.6):
|
- firebase_messaging (15.1.6):
|
||||||
@ -132,6 +132,8 @@ PODS:
|
|||||||
- GoogleUtilities/UserDefaults (8.0.2):
|
- GoogleUtilities/UserDefaults (8.0.2):
|
||||||
- GoogleUtilities/Logger
|
- GoogleUtilities/Logger
|
||||||
- GoogleUtilities/Privacy
|
- GoogleUtilities/Privacy
|
||||||
|
- in_app_review (2.0.0):
|
||||||
|
- FlutterMacOS
|
||||||
- livekit_client (2.3.2):
|
- livekit_client (2.3.2):
|
||||||
- flutter_webrtc
|
- flutter_webrtc
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
@ -186,6 +188,7 @@ DEPENDENCIES:
|
|||||||
- flutter_webrtc (from `Flutter/ephemeral/.symlinks/plugins/flutter_webrtc/macos`)
|
- flutter_webrtc (from `Flutter/ephemeral/.symlinks/plugins/flutter_webrtc/macos`)
|
||||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||||
- gal (from `Flutter/ephemeral/.symlinks/plugins/gal/darwin`)
|
- gal (from `Flutter/ephemeral/.symlinks/plugins/gal/darwin`)
|
||||||
|
- in_app_review (from `Flutter/ephemeral/.symlinks/plugins/in_app_review/macos`)
|
||||||
- livekit_client (from `Flutter/ephemeral/.symlinks/plugins/livekit_client/macos`)
|
- livekit_client (from `Flutter/ephemeral/.symlinks/plugins/livekit_client/macos`)
|
||||||
- media_kit_libs_macos_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos`)
|
- media_kit_libs_macos_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos`)
|
||||||
- media_kit_native_event_loop (from `Flutter/ephemeral/.symlinks/plugins/media_kit_native_event_loop/macos`)
|
- media_kit_native_event_loop (from `Flutter/ephemeral/.symlinks/plugins/media_kit_native_event_loop/macos`)
|
||||||
@ -243,6 +246,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter/ephemeral
|
:path: Flutter/ephemeral
|
||||||
gal:
|
gal:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/gal/darwin
|
:path: Flutter/ephemeral/.symlinks/plugins/gal/darwin
|
||||||
|
in_app_review:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/in_app_review/macos
|
||||||
livekit_client:
|
livekit_client:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/livekit_client/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/livekit_client/macos
|
||||||
media_kit_libs_macos_video:
|
media_kit_libs_macos_video:
|
||||||
@ -279,20 +284,21 @@ SPEC CHECKSUMS:
|
|||||||
file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d
|
file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d
|
||||||
Firebase: cf1b19f21410b029b6786a54e9764a0cacad3c99
|
Firebase: cf1b19f21410b029b6786a54e9764a0cacad3c99
|
||||||
firebase_analytics: a80b3d6645f2f12d626fde928b61dae12e5ea2ef
|
firebase_analytics: a80b3d6645f2f12d626fde928b61dae12e5ea2ef
|
||||||
firebase_core: e4a35c426636a2cce00a5163df7ba69bfd0cca57
|
firebase_core: 1dfe1f4d02ad78be0277e320aa3d8384cf46231f
|
||||||
firebase_messaging: 61f678060b69a7ae1013e3a939ec8e1c56ef6fcf
|
firebase_messaging: 61f678060b69a7ae1013e3a939ec8e1c56ef6fcf
|
||||||
FirebaseAnalytics: 3feef9ae8733c567866342a1000691baaa7cad49
|
FirebaseAnalytics: 3feef9ae8733c567866342a1000691baaa7cad49
|
||||||
FirebaseCore: e0510f1523bc0eb21653cac00792e1e2bd6f1771
|
FirebaseCore: e0510f1523bc0eb21653cac00792e1e2bd6f1771
|
||||||
FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2
|
FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2
|
||||||
FirebaseInstallations: 6ef4a1c7eb2a61ee1f74727d7f6ce2e72acf1414
|
FirebaseInstallations: 6ef4a1c7eb2a61ee1f74727d7f6ce2e72acf1414
|
||||||
FirebaseMessaging: f8a160d99c2c2e5babbbcc90c4a3e15db036aee2
|
FirebaseMessaging: f8a160d99c2c2e5babbbcc90c4a3e15db036aee2
|
||||||
flutter_udid: 6b2b89780c3dfeecf0047bdf93f622d6416b1c07
|
flutter_udid: 2e7b3da4b5fdfba86a396b97898f5fe8f4ec1a52
|
||||||
flutter_webrtc: 53c9e1285ab32dfb58afb1e1471416a877e23d7a
|
flutter_webrtc: 53c9e1285ab32dfb58afb1e1471416a877e23d7a
|
||||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||||
gal: 61e868295d28fe67ffa297fae6dacebf56fd53e1
|
gal: 6a522c75909f1244732d4596d11d6a2f86ff37a5
|
||||||
GoogleAppMeasurement: 987769c4ca6b968f2479fbcc9fe3ce34af454b8e
|
GoogleAppMeasurement: 987769c4ca6b968f2479fbcc9fe3ce34af454b8e
|
||||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||||
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
|
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
|
||||||
|
in_app_review: a6a031b9acd03c7d103e341aa334adf2c493fb93
|
||||||
livekit_client: 9fdcb22df3de55e6d4b24bdc3b5eb1c0269d774a
|
livekit_client: 9fdcb22df3de55e6d4b24bdc3b5eb1c0269d774a
|
||||||
media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82
|
media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82
|
||||||
media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5
|
media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5
|
||||||
|
@ -31,16 +31,18 @@
|
|||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
<string>NSApplication</string>
|
<string>NSApplication</string>
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
<string>Grant access to Photo Library will allow Solian take photo or video for your post.</string>
|
<string>Grant access to Camera will allow Solian use your camera during a call.</string>
|
||||||
<key>NSMicrophoneUsageDescription</key>
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
<string>Grant access to Photo Library will allow Solian record audio for your post.</string>
|
<string>Grant access to Microphone will allow Solian use your microphone during a call.</string>
|
||||||
|
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||||
|
<string>Grant access to Photo Library will allow Solian download photo to album for you.</string>
|
||||||
<key>NSPhotoLibraryUsageDescription</key>
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
<string>Grant access to Photo Library will allow Solian upload photo or video for your post.</string>
|
<string>Grant access to Photo Library will allow Solian upload photo or video for your post.</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
<string>$(PRODUCT_NAME)</string>
|
<string>$(PRODUCT_NAME)</string>
|
||||||
|
<key>NSCameraUseContinuityCameraDeviceType</key>
|
||||||
|
<string></string>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
|
||||||
<string>Grant access to Photo Library will allow Solian download photo to album for you.</string>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|